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.
|