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