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



readpng_init()

The ``real'' code in the basic PNG reader begins when the image file is opened (in binary mode!) and its stream pointer passed to our libpng-initialization routine, readpng_init(). readpng_init() also takes two pointers to long integers representing the height and width of the image:

int readpng_init(FILE *infile, long *pWidth, long *pHeight)

We can get away with using longs instead of unsigned longs because the PNG specification requires that image dimensions not exceed 231 - 1.[99] readpng_init() returns a status value; zero will be used to indicate success, and various nonzero values will indicate different errors.

[99] Of course, an image with dimensions that big is likely to exhaust the real and virtual memory on most systems, but we won't worry about that here.

The first thing we do in readpng_init() is read the first 8 bytes of the file and make sure they match the PNG signature bytes; if they don't, there is no need to waste time setting up libpng, allocating memory and so forth. Ordinarily one would read a block of 512 bytes or more, but libpng does its own buffered reads and requires that no more than 8 bytes have been read before handing off control. So 8 bytes it is:

    uch sig[8];

    fread(sig, 1, 8, infile);
    if (!png_check_sig(sig, 8))
        return 1;   /* bad signature */

There are two things to note here. The first is the use of the uch typedef, which stands for unsigned char; we use it for brevity and will likewise employ ush and ulg for unsigned short and unsigned long, respectively.[100] The second is that png_check_sig() and its slightly more general sibling png_sig_cmp() are unique among libpng routines in that they require no reference to any structures, nor any knowledge of the state of the PNG stream.

[100] Other typedefs, such as uchar and u_char, are more common and recognizable, but these are sometimes also defined by system header files. Unlike macros, there is no way to test for the existence of a C typedef, and a repeated or conflicting typedef definition is treated as an error by most compilers.

Assuming the file checked out with a proper PNG signature, the next thing to do is set up the PNG structs that will hold all of the basic information associated with the PNG image. The use of two or three structs instead of one is historical baggage; a future, incompatible version of the library is likely to hide one or both from the user and perhaps instead employ an image ID tag to keep track of multiple images. But for now two are necessary:

    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL,
      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 */
    }

The struct at which png_ptr points is used internally by libpng to keep track of the current state of the PNG image at any given moment; info_ptr is used to indicate what its state will be after all of the user-requested transformations are performed. One can also allocate a second information struct, usually referenced via an end_ptr variable; this can be used to hold all of the PNG chunk information that comes after the image data, in case it is important to keep pre- and post-IDAT information separate (as in an image editor, which should preserve as much of the existing PNG structure as possible). For this application, we don't care where the chunk information comes from, so we will forego the end_ptr information struct and direct everything to info_ptr.

One or both of png_ptr and info_ptr are used in all remaining libpng calls, so we have simply declared them global in this case:

static png_structp png_ptr;
static png_infop info_ptr;

Global variables don't work in reentrant programs, where the same routines may get called in parallel to handle different images, but this demo program is explicitly designed to handle only one image at a time.

The Dark Side

Let's take a brief break in order to make a couple of points about programming practices, mostly bad ones. The first is that old versions of libpng (pre-1.0) required one to allocate memory for the two structs manually, via malloc() or a similar function. This is strongly discouraged now. The reason is that libpng continues to evolve, and in an environment with shared or dynamically linked libraries (DLLs), a program that was compiled with an older version of libpng may suddenly find itself using a new version with larger or smaller structs. The png_create_XXXX_struct() functions allow the version of the library that is actually being used to allocate the proper structs for itself, avoiding many problems down the road.

Similarly, old versions of libpng encouraged or even required the user to access members of the structs directly--for example, the image height might be available as info_ptr->height or png_ptr->height or even (as in this case) both! This was bad, not only because similar struct members sometimes had different values that could change at different times, but also because any program that is compiled to use such an approach effectively assumes that the same struct member is always at the same offset from the beginning of the struct. This is not a serious problem if the libpng routines are statically linked, although there is some danger that things will break if the program is later recompiled with a newer version of libpng. But even if libpng itself never changes the definition of the struct's contents, a user who compiles a new DLL version with slightly different compilation parameters--for example, with structure-packing turned on--may have suddenly shifted things around so they appear at new offsets. libpng can also be compiled with certain features disabled, which in turn eliminates the corresponding structure members from the definition of the structs and therefore alters the offsets of any later structure members. And I already mentioned that libpng is evolving: new things get added to the structs periodically, and perhaps an existing structure member is found to have been defined with an incorrect size, which is then corrected. The upshot is that direct access to struct members is very, very bad. Don't do it. Don't let your friends do it. We certainly won't be doing it here.

The pointers are now set up and pointing at allocated structs of the proper sizes--or else we've returned to the main program with an error. The next step is to set up a small amount of generic error-handling code. Instead of depending on error codes returned from each of its component functions, libpng employs a more efficient but rather uglier approach involving the setjmp() and longjmp() functions. Defined in the standard C header file setjmp.h (which is automatically included in pngconf.h, itself included in png.h), these routines effectively amount to a giant goto statement that can cross function boundaries. This avoids a lot of conditional testing (if (error) return error;), but it can make the program flow harder to understand in the case of errors. Nevertheless, that's what libpng uses by default, so that's what we use here:

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

The way to read this code fragment is as follows: the first time through, the setjmp() call saves the state of the program (registers, stack, and so on) in png_ptr->jmpbuf and returns successfully--that is, with a return value of zero--thus avoiding the contents of the if-block. But if an error later occurs and libpng invokes longjmp() on the same copy of png_ptr->jmpbuf, control suddenly returns to the if-block as if setjmp() had just returned, but this time with a nonzero return value. The if-test then evaluates to TRUE, so the PNG structs are destroyed and we return to the main program.

But wait! Didn't I just finish lecturing about the evils of direct access to structure members? Yet here I am, referring to the jmpbuf member of the main PNG struct. The reason is that there is simply no other way to get a pointer to the longjmp buffer in any release of libpng through version 1.0.3. And, sadly, there may not be any clean and backward-compatible way to work around this limitation in future releases, either. The unfortunate fact is that the ANSI committee responsible for defining the C language and standard C library managed to standardize jmp_buf in such a way that one cannot reliably pass pointers to it, nor can one be certain that its size is constant even on a single system. In particular, if a certain macro is defined when libpng is compiled but not for a libpng-using application, then jmp_buf may have different sizes when the application calls setjmp() and when libpng calls longjmp(). The resulting inconsistency is more likely than not to cause the application to crash.

The solution, which is already possible with current libpng releases and will probably be required as of some future version, is to install a custom error handler. This is simply a user function that libpng calls instead of its own longjmp()-based error handler whenever an error is encountered; like longjmp(), it is not expected to return. But there is no problem at all if the custom error handler itself calls longjmp(): since this happens within the application's own code space, its concept of jmp_buf is completely consistent with that of the code that calls setjmp() elsewhere in the application. Indeed, there is no longer any need to use the jmpbuf element of the main libpng struct with this approach--the application can maintain its own jmp_buf. I will demonstrate this safer approach in Chapter 14, "Reading PNG Images Progressively".

Note the use of png_destroy_read_struct() to let libpng free any memory associated with the PNG structs. We used it earlier, too, for cases in which creating the info struct failed; then we passed png_ptr and two NULLs. Here we pass png_ptr, info_ptr and one NULL. Had we allocated the second info struct (end_ptr), the third argument would point at it, or, more precisely, at its pointer, so that end_ptr itself could be set to NULL after the struct is freed.

Having gotten all of the petty housekeeping details out of the way, we next set up libpng so it can read the PNG file, and then we begin doing so:

    png_init_io(png_ptr, infile);
    png_set_sig_bytes(png_ptr, 8);
    png_read_info(png_ptr, info_ptr);

The png_init_io() function takes our file stream pointer (infile) and stores it in the png_ptr struct for later use. png_set_sig_bytes() lets libpng know that we already checked the 8 signature bytes, so it should not expect to find them at the current file pointer location.

png_read_info() is the first libpng call we've seen that does any real work. It reads and processes not only the PNG file's IHDR chunk but also any other chunks up to the first IDAT (i.e., everything before the image data). For colormapped images this includes the PLTE chunk and possibly tRNS and bKGD chunks. It typically also includes a gAMA chunk; perhaps cHRM, sRGB, or iCCP; and often tIME and some tEXt chunks. All this information is stored in the information struct and some in the PNG struct, too, but for now, all we care about is the contents of IHDR--specifically, the image width and height:

    png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth,
      &color_type, NULL, NULL, NULL);
    *pWidth = width;
    *pHeight = height;

    return 0;

Once again, since this is a single-image program, I've been lazy and used global variables not only for the image dimensions but also for the image's bit depth (bits per sample--R, G, B, A, or gray--or per palette index, not per pixel) and color type. The image dimensions are also passed back to the main program via the last two arguments of readpng_init(). The other two variables will be used later. If we were interested in whether the image is interlaced or what compression and filtering methods it uses, we would use actual values instead of NULLs for the last three arguments to png_get_IHDR(). Note that the PNG 1.0 and 1.1 specifications define only a single allowed value (0) for either the compression type or the filtering method. In this context, compression type 0 is the deflate method with a maximum window size of 32 KB, and filtering method 0 is PNG's per-row adaptive method with five possible filter types. See Chapter 9, "Compression and Filtering", for details.

That wraps up our readpng_init() function. Back in the main program, various things relating to the windowing system are initialized, but before the display window itself is created, we potentially make one more readpng call to see if the image includes its own background color. In fact, this function could have been incorporated into readpng_init(), particularly if all program parameters used by the back-end readpng functions and the front-end display routines were passed via an application-specific struct, but we didn't happen to set things up that way. Also, note that this second readpng call is unnecessary if the user has already specified a particular background color to be used. In this program, a simple command-line argument is used, but a more sophisticated application might employ a graphical color wheel, RGB sliders, or some other color-choosing representation.




Last Update: 2010-Nov-26