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



writepng_init()

Back in the main program we conditionally fill in various elements of our mainprog_info struct based on the user's command-line options: interlaced, modtime, have_time, gamma, bg_red, bg_green, bg_blue, and have_bg. Note that have_bg is set only if the user provides a background color and the PNM image type is the experimental ``type 8'' binary RGBA file. Also, whereas pnmtopng currently requires the user to provide a text version of the current time for use in the tIME chunk, wpng automatically determines the current time if the -time option is given:

    if (user_specified_time_option) {
        wpng_info.modtime = time(NULL);
        wpng_info.have_time = TRUE;
    }

After finishing the command-line options, we next open the input file (in binary mode!), verify that it's in the proper format, and read its basic parameters: image height, width, and depth. We also generate an output filename based on the input name and verify both that the output file does not already exist and that it can be opened and written to (also in binary mode!). That provides enough information to fill in most of the rest of mainprog_info: infile, pnmtype, have_bg, width, height, sample_depth, and outfile.

If any errors have occurred by this point, wpng prints the usage screen--including the libraries' version information--and exits. Otherwise it optionally prompts the user for PNG text information and then, finally, calls our PNG initialization routine, writepng_init(). It is declared as follows:

int writepng_init(mainprog_info *mainprog_ptr)

where mainprog_ptr just points at the mainprog_info struct we filled in in the main program. writepng_init() begins with some fairly standard libpng boilerplate:

    png_structp  png_ptr;
    png_infop  info_ptr;

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

    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
        png_destroy_write_struct(&png_ptr, NULL);
        return 4;
    }

This fragment allocates memory for the two internal structures that libpng currently requires and sets up a custom error handler. Note that while the structs have the same names and types as those used in our PNG-reading demo programs, libpng provides separate functions to create and destroy them. The first function, png_create_write_struct(), also checks that the compile-time and runtime versions of libpng are reasonably compatible. Of course, any change to the library may create unforeseen incompatibilities, so passing this test does not absolutely guarantee that everything will work. Failing it, on the other hand, is a pretty good indication that things will break.

The second and third arguments to png_create_write_struct() are the keys to installing a custom error handler. The second argument is a pointer to application data (mainprog_ptr, in this case) that will be supplied to the error handler; the third argument is the custom error-handling routine itself. I will explain why it is important to use a custom routine as soon as we take a look at the next section of code.

Once the structs have been allocated, it is necessary to set up the ``receiving end'' of the error-handling code for this particular function. Essentially every user function that calls a libpng routine will need code like this; it amounts to more standard boilerplate, and in general, the only difference between applications will be where the jmpbuf member is stored. In this program, as with the one in the previous chapter, we store jmpbuf in our own struct instead of relying on the one in the main PNG struct:

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

I discussed the semantics of setjmp() and longjmp() in Chapter 13, "Reading PNG Images"; effectively they amount to a really big goto statement. The problem is not so much with the precise storage location of jmpbuf, but rather that its type, jmp_buf, can be different sizes depending on whether certain sytem macros have been defined. When one uses the default libpng error handler, setjmp() is called from the application, but longjmp() is called from within libpng. Since it is not uncommon for the library to be compiled separately from the application--indeed, it may not even have been compiled on the same system--there is no guarantee that the jmp_buf sizes in libpng and the application will be consistent. If they are not, mayhem ensues. See the sidebar for a solution.

writepng_error_handler()

The solution is a ``custom'' error handler, though that's a slight misnomer in our case. Completely custom error handlers can certainly be installed, but libpng currently assumes that its error-handling routine will never return. This rather drastically limits the options for alternatives--basically, one can use longjmp() or exit(), which amounts to an even larger goto statement.[104] Here, as in Chapter 14, "Reading PNG Images Progressively", I have merely taken libpng's default error handler and modified it slightly to use mainprog_ptr instead of png_ptr:

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

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

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

    longjmp(mainprog_ptr->jmpbuf, 1);
}

Because we have to use a libpng function, however trivial, to retrieve our pointer, there is an extra block of code in our version that makes sure the pointer is not NULL. If it is, we are completely stuck, and our only real option is to exit. But assuming the pointer seems valid (it may have been overwritten with an invalid but non-NULL address, in which case we're going to ``exit'' whether we want to or not), we use our saved jmp_buf and longjump back to the part of our application that most recently invoked setjmp(). The key difference from using libpng's error handler is simply the location of the longjmp() call. Here we call both setjmp() and longjmp() within the same application--indeed, from within the same source file. They are therefore guaranteed to have consistent notions of how a jmp_buf is defined, so we have eliminated one more potential source of very-difficult-to-debug crashes.

[104] Ford's Model T was also renowned for its wide range of color options.

As long as we're on the subject of alternatives, libpng also supports user-defined input/output functions. But its default is to read from or write to PNG files, and since that is precisely what we want to do here, I chose to stick with the standard I/O-initialization call and pass the output file's pointer to libpng:

    png_init_io(png_ptr, mainprog_ptr->outfile);

Next we deal with compression. libpng has pretty good defaults, and many programs (possibly most) will not need to do anything here. But in our case we're converting from an uncompressed image format to PNG; for any given image, we're unlikely to do so more than once, and even if we convert many images, wpng is a command-line program and can easily be incorporated into a script for batch processing. Thus I chose to override libpng's default compression setting (zlib level 6--see Chapter 9, "Compression and Filtering") with the slower ``maximum'' setting (zlib level 9):

    png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);

Note that a good PNG-writing program should let the user decide whether and how to override the default settings; options for very fast saves and/or for maximal compression might be reasonable, in addition to the default. In fact, pnmtopng provides options to do just that.

Tweaking Compression

Closely related to compression is filtering, one area in which it is almost always better to leave the decision up to libpng. Repeated tests have shown that filtering is almost never useful on palette-based images, but on everything else it is quite beneficial. Though libpng allows one to restrict its filter selection, this is rarely a good idea; dynamic filtering works best when the encoder can choose from the five defined filter types. But for programmers who want to play with the alternatives, here's an example:

/*
    >>> this is pseudo-code
    if (palette image, i.e., don't want filtering) {
        png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE,
          PNG_FILTER_NONE);
        png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY);
    } else {
        >>> leave default filter selection alone
        png_set_compression_strategy(png_ptr, Z_FILTERED);
    }
 */

The calls to png_set_compression_strategy() actually alter zlib's behavior to work better with the filtered output. Other zlib parameters can also be tweaked, at least in theory; these include the sliding window size, memory level, and compression method. For the last, only method 8 is currently defined, but zlib 2.0 is likely to introduce at least one or two new methods when it is eventually released. Of course, unless and until the PNG specification is revised accordingly, no new compression method can be used within a PNG file without invalidating it.

The window size is the only thing a normal PNG encoder should consider changing, and then only when the total size of the image data, plus one extra byte per row for the row filters, amounts to 16 kilobytes or less. In such a case, the encoder can use a smaller power-of-two window size without affecting compression, which allows decoders to reduce their memory usage. The following fragment shows how to modify these zlib parameters; the values shown are the defaults used by libpng (consult the libpng documentation, specifically ``Configuring zlib'' and ``Controlling row filtering''):

/*
    >>> second arg is power of two; 8 through 15 (256-32768) valid
    png_set_compression_window_bits(png_ptr, 15);
    png_set_compression_mem_level(png_ptr, 8);
    png_set_compression_method(png_ptr, 8);
 */

The next step is to convert our notion of the image type into something libpng will understand. In this case, because we support only three basic image types--grayscale, RGB, or RGBA--we have a one-to-one correspondence between input and output types, so setting the PNG color type is easy. For more general programs, libpng provides several PNG_COLOR_MASK_* macros that can be combined to get the color type, with the exception that PNG_COLOR_MASK_PALETTE and PNG_COLOR_MASK_ALPHA are incompatible. We also set the appropriate PNG interlace type if the user so requested:

    int color_type, interlace_type;

    if (mainprog_ptr->pnmtype == 5)
        color_type = PNG_COLOR_TYPE_GRAY;
    else if (mainprog_ptr->pnmtype == 6)
        color_type = PNG_COLOR_TYPE_RGB;
    else if (mainprog_ptr->pnmtype == 8)
        color_type = PNG_COLOR_TYPE_RGB_ALPHA;
    else {
        png_destroy_write_struct(&png_ptr, &info_ptr);
        return 11;
    }

    interlace_type = mainprog_ptr->interlaced? PNG_INTERLACE_ADAM7 :
                                               PNG_INTERLACE_NONE;

At this point, we can set the basic image parameters. We have the option of using several functions, each of which sets a single parameter, but there is really no point in doing so. Instead we set all of them with a single call to png_set_IHDR():

    png_set_IHDR(png_ptr, info_ptr, mainprog_ptr->width,
      mainprog_ptr->height, mainprog_ptr->sample_depth,
      color_type, interlace_type,
      PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

If we supported palette-based images, this is the point at which we would define the palette for libpng, via the png_set_PLTE() and possibly png_set_tRNS() functions. We can also set any optional parameters the user specified, starting with the gamma value, background color, and image modification time. In the case of the background color, we know that have_bg will be true only if the image has an alpha channel; in this program, that necessarily implies that it's an RGBA image, not grayscale with alpha or palette-based with transparency. Thus we only fill in the red, green, and blue elements of the png_color_16 struct:

    if (mainprog_ptr->gamma > 0.0)
        png_set_gAMA(png_ptr, info_ptr, mainprog_ptr->gamma);

    if (mainprog_ptr->have_bg) {
        png_color_16  background;

        background.red = mainprog_ptr->bg_red;
        background.green = mainprog_ptr->bg_green;
        background.blue = mainprog_ptr->bg_blue;
        png_set_bKGD(png_ptr, info_ptr, &background);
    }

    if (mainprog_ptr->have_time) {
        png_time  modtime;

        png_convert_from_time_t(&modtime, mainprog_ptr->modtime);
        png_set_tIME(png_ptr, info_ptr, &modtime);
    }

It is also worth noting that libpng copies most of the data it needs into its own structs, so we can get away with using temporary variables like background and modtime without worrying about their values being corrupted before libpng is ready to write them to the file. The only exceptions are things involving pointers, in which case libpng copies the pointer itself but not the buffer to which it points. In fact, libpng's text-handling code is an excellent example of that:

    if (mainprog_ptr->have_text) {
        png_text  text[6];
        int  num_text = 0;

        if (mainprog_ptr->have_text & TEXT_TITLE) {
            text[num_text].compression = PNG_TEXT_COMPRESSION_NONE;
            text[num_text].key = "Title";
            text[num_text].text = mainprog_ptr->title;
            ++num_text;
        }
        if (mainprog_ptr->have_text & TEXT_AUTHOR) {
            text[num_text].compression = PNG_TEXT_COMPRESSION_NONE;
            text[num_text].key = "Author";
            text[num_text].text = mainprog_ptr->author;
            ++num_text;
        }
        if (mainprog_ptr->have_text & TEXT_DESC) {
            text[num_text].compression = PNG_TEXT_COMPRESSION_NONE;
            text[num_text].key = "Description";
            text[num_text].text = mainprog_ptr->desc;
            ++num_text;
        }
        if (mainprog_ptr->have_text & TEXT_COPY) {
            text[num_text].compression = PNG_TEXT_COMPRESSION_NONE;
            text[num_text].key = "Copyright";
            text[num_text].text = mainprog_ptr->copyright;
            ++num_text;
        }
        if (mainprog_ptr->have_text & TEXT_EMAIL) {
            text[num_text].compression = PNG_TEXT_COMPRESSION_NONE;
            text[num_text].key = "E-mail";
            text[num_text].text = mainprog_ptr->email;
            ++num_text;
        }
        if (mainprog_ptr->have_text & TEXT_URL) {
            text[num_text].compression = PNG_TEXT_COMPRESSION_NONE;
            text[num_text].key = "URL";
            text[num_text].text = mainprog_ptr->url;
            ++num_text;
        }
        png_set_text(png_ptr, info_ptr, text, num_text);
    }

Here I have declared a temporary array of six png_text structs, each of which consists of four elements: compression, key, text, and text_length. The first of these simply indicates whether the text chunk is to be compressed (zTXt) or not (tEXt). key and text are pointers to NULL-terminated strings containing the keyword and actual text, respectively. These pointers are what libpng copies, but the text buffers to which they point must remain valid until either png_write_info() or png_write_end() is called--we'll return to that point in a moment. The final member of the struct, text_length, is used internally by libpng; we need not set it ourselves, since libpng will do so regardless.

Anywhere from one to six of the structs is filled in, depending on whether the main program set the appropriate bit for each of the six supported keywords. Then png_set_text() is called, which triggers libpng to allocate its own text structs and copy our struct data into them. Alternatively, we could have used a single png_text struct, repeatedly filling it in and calling png_set_text() for each keyword; libpng merely chains the copied text structs together, so the net result would have been the same.

Text Buffers, PNG Structs, and Core Dumps

The issue of libpng's allocation of its own text buffers is worth a closer look, because it indirectly led to a subtle but fatal bug in a popular PNG viewer. The program in question was John Bradley's XV, an elegant and powerful image viewer/converter for the X Window System. Version 3.10a, released late in 1994 and still the most recent release as of this writing, had no native PNG support. But because it was available in source-code form, it was one of the first applications to support the reading and writing of PNGs, thanks to a patch created by Alexander Lehmann in June 1995 and later modified by Andreas Dilger and the author of this book.

This patch was originally written to work with libpng 0.71 and zlib 0.93, beta versions so old they were arguably alpha-level software. At the time, major functionality was still being added to libpng, and the so-called modern ``convenience functions'' for modifying libpng parameters did not exist. As a result, the patch was designed to access the two PNG structs directly, and later updates to the patch did not completely eliminate this behavior. In particular, all versions of the patch through 1.2d, released in June 1996, allocated their own text structs and plugged them directly into one of the main PNG structs for libpng's use.

Now fast-forward to January 1998, when the final libpng betas were being released. By this time, libpng provided functions not only to allocate and destroy the PNG structs, but also to read from them and write to them. In particular, png_set_text() already existed in its present form; i.e., it allocated its own text structs and copied the user-supplied data into them. But one of the changes in libpng 0.97 involved plugging some small memory leaks by freeing these libpng-allocated text structs as part of png_destroy_write_struct(). Unfortunately, libpng had no way to track whether it had actually allocated the structs in the first place, and...well, one can see where this is going. First libpng freed the text structs, then the XV patch--which had allocated them--did so again. Boom: segmentation fault, core dump, an incomplete PNG file, and no more XV.

The moral of this little story is simple: 1995-era programs had no choice but to access libpng structs directly, because that was how libpng was originally written. But modern programs should never do so, not only because of this particular problem, but also for the several other reasons detailed in the previous two chapters. Let's say it again: Accessing libpng structures directly is just plain evil. Don't do it!

Ye have been warned.

The setting of the text chunks is our last piece of non-pixel-related PNG information, so our next step is to write all chunks up to the first IDAT:

    png_write_info(png_ptr, info_ptr);

Doing this flushes any time or text chunks to the output file, and the corresponding data in the PNG structs is marked so that it is not written to the file again later. I mentioned earlier that text buffers must remain valid until either png_write_info() or png_write_end() is called, which implies that either one can be used to write text chunks to the PNG file. This is indeed the case. Had we wished to put all of our text chunks (or the time chunk) at the end of the PNG file, we would have called png_write_info() first, followed by one or both of png_set_tIME() and png_set_text().

In the case of the latter function,[105] we could do both--that is, call it with one or more text structs before calling png_write_info() and then call it again with one or more new text structs (perhaps a lengthy legal disclaimer to be stored in a zTXt chunk) afterward. Any calls to png_set_text() occurring before png_write_info() will be written to the PNG file before the IDATs; any calls to it after png_write_info() but before png_write_end() will be written to the PNG file after the IDATs. And any png_set_text() or png_set_tIME() calls after png_write_end() will be ignored.

[105] Recall from Chapter 11, "PNG Options and Extensions", that only one tIME chunk is allowed.

Having completed our pre-IDAT housekeeping, we can now turn to our image-data transformations. But unlike our PNG-reading demos, most programs that write PNGs will not require many transformations. In fact, we only call one, and technically there's no point even in that:

    png_set_packing(png_ptr);

This function packs low-bit-depth pixels into bytes. There are no low-bit-depth RGB and RGBA images; only grayscale and palette images support bit depths of 1, 2, or 4. But our main program neither counts colors to see whether a palette-based representation would be possible, nor checks for valid low-bit-depth grayscale values, and it always sets sample_depth to 8, so there is currently no possibility of libpng actually being able to pack any pixels. However, pnmtopng does both, and perhaps a subsequent revision of wpng will, too.

The only remaining thing for our initialization function to do is to save copies of the two PNG-struct pointers for passing to libpng functions later:

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

    return 0;

Once again, we could have used global variables instead, but this program is intended to demonstrate how a multithreaded PNG encoder might be written.




Last Update: 2010-Nov-26