Channels ▼


PNG: The Portable Network Graphic Format

Source Code Accompanies This Article. Download It Now.

JUL95: PNG: The Portable Network Graphic Format

PNG: The Portable Network Graphic Format

Hey, didn't you used to be GIF?

Lee Daniel Crocker

Lee was involved with defining the GIF89a and JFIF image file formats. He works for Avantos Performance Systems and can be reached at [email protected]

On December 29, 1994, CompuServe announced new licensing terms for use of the Graphics Interchange File (GIF) format, a previously freely available de facto standard. Unisys Corp. instigated this with its decision to enforce its U.S. Patent 4,558,302 on the LZW (Lempel-Ziv-Welch) compression algorithm. Consequently, developers of GIF-based software have to begin paying royalties to Unisys or sublicensing fees to CompuServe. Even producers of CD-ROMs with public-domain software must pay if that software uses GIF. This upset many developers who have been using GIF free-of-charge since its creation in 1987. The change affected software developers, graphic artists, Internet and online-service users, and others. The Usenet newsgroup quickly filled with discussion, leading to a mailing list looking into ways of replacing GIF. Many of us joined the effort, which has since received CompuServe's official support.

We first considered basing our work on an existing file format, but quickly discovered that existing formats failed to fully address the issues that made GIF so popular. Portability was our first concern. Formats such as BMP, ILBM, PCX, PICT, and others have explicit/implicit platform dependencies. The Microsoft/Aldus Tagged Image File Format (TIFF) can hold any kind of graphic information in a platform-independent way. However, almost no application supports every TIFF option, so many low-end programs can't read files written by high-end programs or programs on different platforms. TIFF also requires file seeks in order to read, making it difficult to use in streaming communications applications such as World-Wide Web browsers. GIF, having been designed by CompuServe, had communications in mind from the outset. In fact, some images transferred from online services or Web sites to user displays never exist as a file at all.

We then considered JPEG File Interchange Format (JFIF). It is designed for streaming applications and offers outstanding quality and compression. Many applications that currently use GIF--such as distributing photographs from online services and BBSs--would be far better served by JFIF. Unfortunately, JFIF is optimal only for photographic images (grayscale or full color); it performs badly with line art, text, icons, and similar images.

JFIF is also less useful than GIF for medium-quality photographs that need to be edited extensively. While converting a full-color photo to GIF incurs considerable loss of data (a process called "color quantization"), any further editing of the GIF is lossless. JFIF, while starting at a much-higher quality than GIF, loses more data every time an image is edited, much like a photocopy of a photocopy. Even simple cropping is not immune. JFIF's rarely used lossless mode suffers from the TIFF problem: Very few programs read them.

With all this in mind, we decided to create a new format, one that would be:

  • Simple, clean, and easy to implement.
  • Completely portable.
  • Available free-of-charge in source-code form for reading and writing.
Furthermore, we wanted the format to:

  • Support 100 percent lossless conversion of existing GIFs.
  • Support streaming communications.
  • Compress better than GIF, while still being lossless.
  • Support progressive display and transparency as well as or better than GIF.
Most importantly, the new format needed to be patent free, public, and otherwise legally unencumbered.

We achieved these goals and more with the Portable Network Graphic (PNG) format. In addition, we provided support for lossless compression of full-color and medical images. This addition slightly increased the format's complexity, but it greatly widened the potential market and addressed serious omissions in GIF. On the other hand, we did not include features better served by other means. JFIF, for example, handles lossy compression well. Because we didn't want to complicate the format with support for multiple images (which are better handled by other tools), PNG is strictly a single-image format. (For specific details, the PNG specification is available on CompuServe at GO GRAPHICS and on the Internet at


To determine which features to include in PNG and how to implement them, I ran more than 6000 tests on different kinds of images, producing files in various formats, so that our ultimate decisions would be based on solid evidence. Other team members ran tests to confirm these results and to test issues such as compression optimization and progressive display.

Many of the test results surprised us. For example, separating scan lines into red, green, and blue sections didn't prove efficient. This surprised me because, during the development of GIF89a spec, I had proposed a similar technique to store full-color data. At that time, scan-line separation was an improvement to compression, but that was with LZW and apparently didn't apply to the deflation algorithm.

Another surprising result was that predictor-function filtering not only failed to help compression of limited-color images, but it made images bigger. This technique works wonders on gray-scale and full-color images, but was disastrous once an image went through quantization. This affected some of the recommendations in the specification and in some current implementations.

I ran another suite of tests to decide whether drawings with few (4 or 16) colors should be stored with their values packed two or four to a byte, or expanded to a byte each before compression. Some team members suspected that the latter might lead to more identical bytes that the Huffman-coding portion of the deflate algorithm could compress well. As it turns out, packing them into bytes worked better, so we allowed it even though we wanted to minimize the number of options.


Compression is one of GIF's most attractive features. Users could always count on files not taking up huge amounts of disk space or download time. While not the best, GIF compression is reliable and good enough for most uses.

We decided to use the deflate-compression scheme Phil Katz designed for PKZIP because it is well documented, freely usable, powerful, and has an existing base of code supporting it on many platforms. The algorithm, called a "sliding-window" method, has a buffer (32K in our case) that is logically passed over the input stream. Output is based on patterns found in that window, which are then Huffman-coded for further compression. This method (without the Huffman pass) was first described by Lempel and Ziv in 1977, but not patented. In 1978, they described a different method of collecting patterns in text that was simpler and almost as powerful. Terry Welch (now with Unisys) described a particular straightforward way to implement their LZ78 idea--the dreaded LZW patent.

To further enhance compression, we added prefiltering of image data through predictor functions. This takes advantage of the slowly varying nature of true-color and gray-scale photographs. In the simplest case (as with TIFF), each pixel is stored as the difference between it and the previous pixel. In images that contain a wide range of values with smooth transitions between them, predictor functions create many more small values for the compressor. Programs reading the file perform the same function in reverse--adding the input value to the previously decoded pixel. As a result, the approach is completely lossless.

Predictor functions differ in their methods. So that PNG display programs would not have to know a dozen filter functions or keep more than one line of data around, we limited the number of functions. We considered only those that outperformed all other predictors by at least 5 percent on a number of images, and from those, we chose the four simple functions now in the specification.

This technique is powerful (sometimes saving 30 percent on file sizes) but dangerous. Certain filters work best on certain images, and it is hard to predict which. When these functions don't work, they make things worse. A PNG-writing program must carefully choose the functions to use for a particular image. One reasonable method is to use none. This will usually generate a file smaller than the equivalent GIF, but perhaps not as small as it could be. Another method is to try each of the functions on the first few input lines, then measure which one works best.

For streaming applications, it's useful not to have to decide in advance which predictor function to use for the entire file. The function ID number for each scan-line is stored at the start of the line, so you can adapt to the image as it comes. My own code does this and picks predictors with a third method as well: a heuristic that looks at the bytes in each line and tries to guess which predictor will work best. It often outperforms even the best of the single predictors because images often have regions which are different from each other. The compression levels we achieved are shown in Table 1.

Progressive Display

Interlacing is another popular GIF feature. If you view an interlaced GIF while it's being downloaded (a feature available in numerous communications packages and Web browsers), you see a 1/8-quality version of the image in 1/8 of the time, then 1/4, 1/2, and finally the whole thing. This helps you decide whether or not to cancel the download.

Through testing, we discovered that we could extend this concept to a seven-pass method that gave quicker response and better-looking intermediates--without adding too much complexity, requiring more memory, or compromising compression. Storing an image this way expands it by about 8 percent, but lets users get a 1/64-quality image almost immediately, followed by 1/32, 1/16, and so on. PNG stores each pass as a rectangular image, so the predictor functions work well, and often overcome the interlace-induced size expansion. This makes implementation simpler, too. As with GIF, it is possible to interlace/de-interlace images by storing the passes in temporary files.


Sometimes you need to overlay an image onto a background, allowing the background to peek through parts of the top image. High-end applications extend this by storing a numerical transparency value for each pixel so that the top image smoothly transitions into the background.

In this instance, we were torn between keeping the specification simple and clean and accommodating an obvious need for these features. We knew that we could have used gradual transparency (also called "alpha channel") for simple on/off transparency, so we considered making it the only option. However, testing revealed that using the GIF-like technique of "keying" (picking a color in the image and treating it as "clear") allowed better compression and was already supported by existing code. Reluctantly, we allowed both.

In any event, simple image-display programs can ignore transparency information, so it isn't that much of a burden.

Gamma Correction

The lack of a standard definition of RGB values has always limited GIF's portability. The numbers you send to your display will look different when sent to your color printer. Likewise, a scanned photograph that looks great on a Macintosh will look dark and murky on a PC. With PNG, we wanted the image to look right on any device.

While there's general agreement that 0 means black and 255 means white, the human eye, TV phosphors, color printers, and film emulsions all disagree about what 128 means. With a linear-response medium (many color printers and Macintosh displays, for instance), the response curve might look like the line in Figure 1, where 128 roughly equates to 50 percent gray, 64 to 25 percent gray, and so on. Linear values are simple, easy to work with, and easy to match. It is not uncommon for a Macintosh to have more than one monitor, and standardizing on linear RGB values means that colors will closely match on all of them.

What's wrong with this method? Well, neither your eyes nor television phosphors work this way. You can get better-looking pictures by using a response curve like the dashed line in Figure 1, where 128 means 73 percent gray and 64 means 54 percent gray. This is how TVs treat input voltages, and the way color values respond on typical PC displays. Unfortunately, there's no single standard for that curve either. With NTSC television cameras, for example, Voltage=(Luminance(1/Gamma)), where Gamma=0.45, and the Luminance and Voltage values are scaled into the 0.0 to 1.0 range. Note that the linear case is the same equation with Gamma=1.0.

We could have picked one of these methods and required developers to convert from one to the other (as JFIF does). However, this conversion process causes some loss of original data. Instead we decided to let programmers put their native values into the file, then provide a clue--the Gamma value--in the file as to which curve was used. If you are writing a PNG file from data designed specifically for a PC or digitized from a television camera, 0.45 is a good choice to put into the file. If you're capturing a Macintosh screen, use 1.0. Artificial images (such as those created by ray-tracing programs) usually use 1.0, as well.

Correcting for gamma when you display an image is important. It isn't necessary to perform complex calculations for every pixel. For palette-based images (all GIFs are palette based, so most PNGs probably will be), you correct the palette and the pixels take care of themselves. Gray-scale and full-color images can be corrected by making a 256-byte lookup table and mapping the pixel values through the table as they come in.

Color experts will tell you that even gamma correction isn't enough to get accurate color matching. Strictly speaking, they're right, but gamma correction will get you 90 percent of the way there, showing colors similar to the original. High-end applications can add high-definition information.

File Layout

A PNG file (or data stream) consists of a signature identifying the file, followed by a series of chunks, each of which contains a specific piece of information about the image. The 8-byte identifying signature (0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A) also detects common transmission problems. For example, if gateways chop 8-bit data to 7 bits or modify end-of-line characters (common problems on the Internet), the receiver will detect this immediately. The long sequence also makes it fairly reliable to find the start of a PNG stream when it is embedded inside another byte stream. Macintosh binary headers--128 bytes of Mac operating-system information added to the start of a binary file--are often attached to files uploaded by Macintosh users to BBSs and Internet sites. A program that reads PNG files (and any other format, for that matter) should accept a file as valid if the identifying number starts 128 bytes into the file instead of at the beginning.

There is no PNG version number. Strict compatibility and version numbers don't mix. However, the existing PNG specification allows for private extension.

The general layout of a chunk is a 4-byte length, followed by a 4-byte chunk-type ID, data bytes (which vary with chunk type), and finally a 4-byte CRC. All 2-byte and 4-byte values are stored high-to-low (Big-endian), so they must be byte-swapped when read into an integer on machines (such as Intel processors) that store the low byte first. The data-length field counts only the number of bytes in the data field, so the total size of the chunk is Data Length+12.

The chunk type consists of four ASCII characters. The case of the characters indicates certain information about the chunk type. Chunks that contain critical information about the image begin with an uppercase letter. Those that contain ancillary information begin with lowercase letters. When a PNG-reader program encounters a chunk it doesn't recognize, it may safely ignore ancillary chunks, although this might not produce as good an image. If the unknown chunk is marked critical, the program should warn the user that vital information is missing, although it may attempt to render the image anyway.

Chunks defined publicly in the PNG specification have an uppercase letter in the second position. Those with a lowercase letter can be used by developers for application-specific needs. The popular fractal-exploration program Fractint, for example, uses a chunk of this type to store the math formula that generates the image. Someone who wants to look at the picture will ignore this chunk. However, if you load the image back into Fractint, the data in the files lets you continue exploring fractal space from where you left off.

The third letter, which is always uppercase, is currently unused. The final letter indicates the copy-safe property of a chunk. Programs that make changes to any critical chunk in a PNG file should look at the fourth letter of any chunk they don't recognize. If that letter is lowercase, it is safe to copy that chunk into the new file even if you don't recognize it. If the chunk ID ends with an uppercase letter, you should not copy that chunk into the new file. This guarantees data integrity of a file that contains chunks that depend on other chunks in the image.

The CRC-32 at the end of each chunk verifies the integrity of the data. This is important for communication channels. Another use for CRCs is detecting changes in the chunks. For instance, say you wanted to store a chunk of application-specific data inside a palette-based image used for palette animation. Because this chunk depends on the palette chunk, you could store the CRC of the palette chunk in your data area and test it to make sure that the palette was not modified. This technique allows you to use copy-safe chunks for things that depend on other data in the file. The ptot program (see ptot.h, Listing One, and ptot.c, Listing Two shows how to do this.

This program, which implements one approach to PNG-to-TIFF conversion, attempts to preserve as much data as possible. No attempt is made to compress the output. I have also borrowed Mark Adler's public-domain inflate.c code from the Info-Zip package. This makes the decompression routines a little less lucid than they might be if they had been an integral part of the PNG-to-TIFF package itself. All of the necessary files to compile the PNG-to-TIFF converter on MS-DOS, Windows NT/POSIX, and Sun OS w/gcc are available electronically; see "Availability," page 3. Be sure to use the appropriate makefile.

Chunk Types

The minimum set of chunks that every PNG file must have is IHDR, IDAT, and IEND, in that order. Palette-based images must have PLTE. All images should have gAMA if it's known, but none if it is not (hence the lowercase "g"). Other chunks may appear anywhere between IHDR and IEND, but some have specific rules about ordering (gAMA must appear before PLTE, and PLTE before IDAT, for example). Because a streaming PNG writer may not know the compressed size of the entire image when it starts writing, it is allowed to split IDAT into multiple chunks. It is not legal to put other chunks in between these IDATs, so a PNG reader can always depend on all the image data being in sequential IDATs.

The pHYs ancillary chunk is used to describe the physical size of the image. A scanner may put in this chunk, for example, that the photo was 5x5 cm. While most simple display programs will ignore this and show the picture pixel-for-pixel, desktop-publishing programs will find this invaluable. Note that a unit of none is allowed in this chunk. If set, the measurements in the pHYs chunk can be used to indicate how far from square the pixels are, even though you don't know the actual size. The oFFs chunk measures the offset of the upper-left corner of the image from the upper-left corner of whatever it is embedded in (a page, a background image, or whatever). This can be handy, for example, when sending an image to a service bureau to be printed or for converting old multiple-image GIFs (which are rare, and not supported by many GIF-reading programs).

Ancillary tEXt chunks store short comments or data such as author's name, software used, and the like. The PNG spec specifies that text should be ISO 8859-1, a common, 8-bit extension of ASCII. Windows ANSI is already a superset of ISO, so it requires no translation. Macintosh and other machines have to use a translation table to ensure that the comment "Woodcut by Albrecht Durer," for example, looks the same on all machines. Care should also be taken with extra characters in the Windows ANSI set that aren't part of ISO; for example, "" and -. Although not portable, they are likely to be found in PNG files, so a Macintosh app should assume they are Windows ANSI. We considered Unicode (especially the ASCII-compatible UTF-8 encoding), but chose not to include it.

If you use more than one line of text in such a block (this isn't recommended), use ASCII newline (LF) characters only. Lines ending with CR (as in Macintosh) or CR/LF (as in MS-DOS) should be converted.

These chunks are intended for short pieces of human-readable information about the image, not presentation-quality text.


Although PNG was primarily designed to replace GIF, it could become a commonly used format, even in applications that do not use GIF. By the time you read this, it will be supported by shareware and commercial authors, and free code will also be available.


Graphics Interchange Format. CompuServe Inc. Columbus, OH, 1990.

Hunt, R.W.G. The Reproduction of Colour in Photography, Printing, and Television. Tolworth, U.K.: Fountain Press, 1987.

JFIF, C-Cube Microsystems, Milpitas, CA, 1987.

Liaw, Wilson. "Reading GIF Files." Dr. Dobb's Journal (February 1995).

Murray, James D. and William vanRyper. Encyclopedia of Graphics File Formats. Sebastopol, CA: O'Reilly & Associates, 1994.

TIFF Revision 6.0, Aldus Corp., Seattle, WA, 1992.

<a name="002f_0012"><B>Table 1:</B> Comparative file sizes (in bytes).<a name="002f_0012">
Name      Subject     Type          PNG      GIF      JPEG        TIFF
                                                    (Lossless) (LZW/Pred)
dpcreek   landscape grayscale     190,178  283,794    202,263    263,175
einstein  face      grayscale     208,139  261,922    213,372    275,970
winona    face      grayscale     249,602  400,653    242,896    338,598
bayros    drawing   grayscale     161,652  239,704    161,659    293,552
laura     figure    256 colors    532,043  613,388    N/A        672,010
olivia    drawing   256 colors    149,480  170,134    N/A        224,740
spiral    fractal   256 colors    665,004  948,736    N/A        842,200
wall      landscape 256 colors    169,513  187,588    N/A        242,740
catalina  figure    full color  1,285,476    N/A    1,448,055  1,512,778
goldhill  landscape full color    703,787    N/A      796,283    892,922
neptune   astronomy full color    295,197    N/A      390,420    413,376
cwheel    raytrace  full color    229,506    N/A      366,144    383,188
<B><a href="/showArticle.jhtml?documentID=ddj9507c&pgno=3">Figure 1</A>: </B>Gamma response.

Listing One

/* ptot.h -- Header file for PNG to TIFF converter.
 * HISTORY: 95-03-10 Created by Lee Daniel Crocker <[email protected]>
 *          <URL:>
#ifdef _X86_            /* Intel i86 family */
#ifdef _SPARC_____LINEEND____
#  define BIG_ENDIAN
#  ifndef FILENAME_MAX /* Work around stupid header file bugs */
#    define FILENAME_MAX 1024
#  endif
#  ifndef SEEK_SET
#    define SEEK_SET 0
#  endif
#  ifndef min
#    define min(x,y) (((x)<(y))?(x):(y))
#  endif
#  ifndef max
#    define max(x,y) (((y)<(x))?(x):(y))
#  endif
/* Some types and macros for easier porting. Byte swapping is the major issue
 * because we have to convert big-endiang PNG to native-endian TIFF on 
 * whatever architecture we're compiled on. Code depends heavily on endianness
 * definition above. Functions would be a lot simpler than macros here, but are
 * less likely to be optimized down to simple inline byte swaps. Some of these
 * macros evaluate the address twice, so don't pass "*p++" to them! */
typedef signed char     S8;
typedef unsigned char   U8;
typedef signed short    S16;
typedef unsigned short  U16;
typedef signed long     S32;
typedef unsigned long   U32;
#ifndef TRUE
#  define TRUE 1
#  define FALSE 0
#define LOBYTE(w)   ((U8)((w)&0xFF))
#define HIBYTE(w)   ((U8)(((w)>>8)&0xFF))
#define LOWORD(d)   ((U16)((d)&0xFFFF))
#define HIWORD(d)   ((U16)(((d)>>16)&0xFFFF))
#define PUT16(p,w)  (*(U16*)(p)=(w))    /* Native byte order */
#define GET16(p)    (*(U16*)(p))
#define PUT32(p,d)  (*(U32*)(p)=(d))
#define GET32(p)    (*(U32*)(p))
#if !defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN)
#  error "No byte order defined"
#  define BE_GET16(p)     GET16(p)
#  define BE_PUT16(p,w)   PUT16((p),(w))
#  define BE_GET32(p)     GET32(p)
#  define BE_PUT32(p,d)   PUT32((p),(d))
#  define LE_GET16(p)     ((U16)(*(U8*)(p)&0xFF)|\
#  define LE_PUT16(p,w)   (((*(U8*)(p))=LOBYTE(w)),\
#  define LE_GET32(p)     (((U32)LE_GET16(p))|\
#  define LE_PUT32(p,d)   (LE_PUT16((p),LOWORD(d)),\
#  define BE_GET16(p)     ((U16)(*(U8*)(p)<<8)|\
#  define BE_PUT16(p,w)   (((*(U8*)(p))=HIBYTE(w)),\
#  define BE_GET32(p)     (((U32)BE_GET16(p)<<16)|\
#  define BE_PUT32(p,d)   (BE_PUT16((p),HIWORD(d)),\
#  define LE_GET16(p)     GET16(p)
#  define LE_PUT16(p,w)   PUT16((p),(w))
#  define LE_GET32(p)     GET32(p)
#  define LE_PUT32(p,d)   PUT32((p),(d))
/* Miscellaneous PNG definitions. */
#define PNG_Signature       "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"
#define PNG_MaxChunkLength  0x7FFFFFFFL
#define PNG_CN_IHDR 0x49484452L     /* Chunk names */
#define PNG_CN_PLTE 0x504C5445L
#define PNG_CN_IDAT 0x49444154L
#define PNG_CN_IEND 0x49454E44L
#define PNG_CN_gAMA 0x67414D41L
#define PNG_CN_sBIT 0x73424954L
#define PNG_CN_cHRM 0x6348524DL
#define PNG_CN_tRNS 0x74524E53L
#define PNG_CN_bKGD 0x624B4744L
#define PNG_CN_hIST 0x68495354L
#define PNG_CN_tEXt 0x74455874L
#define PNG_CN_zTXt 0x7A545874L
#define PNG_CN_pHYs 0x70485973L
#define PNG_CN_oFFs 0x6F464673L
#define PNG_CN_tIME 0x74494D45L
#define PNG_CN_sCAL 0x7343414CL
#define PNG_CF_Ancillary    0x20000000L /* Chunk flags */
#define PNG_CF_Private      0x00200000L
#define PNG_CF_CopySafe     0x00000020L
#define PNG_FT_Adaptive 0   /* Filtering type */
#define PNG_CT_Deflate  0   /* Compression type */
#define PNG_IT_None     0   /* Interlace types */
#define PNG_IT_Costello 1
#define PNG_CB_Palette  0x01    /* Colortype bits */
#define PNG_CB_Color    0x02
#define PNG_CB_Alpha    0x04
#define PNG_MU_None         0   /* Measurement units */
#define PNG_MU_Pixel        0
#define PNG_MU_Meter        1
#define PNG_MU_Micrometer   1
#define PNG_MU_Radian       2
#define PNG_PF_None     0   /* Prediction filters */
#define PNG_PF_Sub      1
#define PNG_PF_Up       2
#define PNG_PF_Average  3
#define PNG_PF_Paeth    4
/* Miscellaneous TIFF definitions. This is a small subset of the tags, data 
 * types, and values available in TIFF--only those needed for PNG conversion.
 * For example, we are not doing any TIFF compression, so the only TIFF 
 * compression type listed is "None". */
#define TIFF_BO_Intel       0x4949  /* Byte order identifiers */
#define TIFF_BO_Motorola    0x4D4D
#define TIFF_MagicNumber    42
#define TIFF_DT_BYTE        1   /* Data types */
#define TIFF_DT_ASCII       2
#define TIFF_DT_SHORT       3
#define TIFF_DT_LONG        4
#define TIFF_DT_RATIONAL    5
#define TIFF_TAG_ImageWidth         256 /* Tag values */
#define TIFF_TAG_ImageLength        257
#define TIFF_TAG_BitsPerSample      258
#define TIFF_TAG_Compression        259
#define TIFF_TAG_PhotometricInterpretation  262
#define TIFF_TAG_ImageDescription   270
#define TIFF_TAG_Make               271
#define TIFF_TAG_Model              272
#define TIFF_TAG_StripOffsets       273
#define TIFF_TAG_SamplesPerPixel    277
#define TIFF_TAG_RowsPerStrip       278
#define TIFF_TAG_StripByteCounts    279
#define TIFF_TAG_XResolution        282
#define TIFF_TAG_YResolution        283
#define TIFF_TAG_PlanarConfiguration        284
#define TIFF_TAG_XPosition          286
#define TIFF_TAG_YPosition          287
#define TIFF_TAG_ResolutionUnit     296
#define TIFF_TAG_TransferFunction   301
#define TIFF_TAG_Software           305
#define TIFF_TAG_DateTime           306
#define TIFF_TAG_Artist             315
#define TIFF_TAG_HostComputer       316
#define TIFF_TAG_WhitePoint         318
#define TIFF_TAG_PrimaryChromaticities      319
#define TIFF_TAG_ColorMap           320
#define TIFF_TAG_ExtraSamples       338
#define TIFF_TAG_Copyright          33432
/* This last tag is registered to me specifically for this program and its 
 * companion TIFF-to-PNG (not included in the DDJ code), so that they can be 
 * invertable. I encourage you to use it for the same purpose--just make sure 
 * you stay compatible with this program. There is no equivalent PNG chunk for
 * TIFF data; the known TIFF tags can be either translated to functionally
 * equivalent PNG chunks or encoded in tEXt chunks. Unknown
 * ones are not copy-safe (according to the TIFF spec). */
#define TIFF_TAG_PNGChunks          34865
#define TIFF_CT_NONE    1   /* Compression type */
#define TIFF_PI_GRAY    1   /* Photometric interpretations */
#define TIFF_PI_RGB     2
#define TIFF_PI_PLTE    3
#define TIFF_PC_CONTIG  1   /* Planar configurations */
#define TIFF_RU_NONE    1   /* Resolution units */
#define TIFF_RU_CM      3
#define TIFF_ES_UNASSOC 2   /* Extra sample type */
/* Structure for holding miscellaneous image information. Conversion program
 * will read an image into this structure, then pass it to the output function.
 * In this implementation, image data bytes are stored in a file, which is 
 * pointed to by this structure. This is so the code will work on small-memory
 * architectures like MS-DOS. On Unix, Win32 (NT/Chicago), and other systems,
 * it might make more sense to allocate one big chunk of memory for the image
 * and replace image_data_file string with an image_data_buffer pointer. */
#define N_KEYWORDS 5
typedef struct _image_info {
    U32 width, height;
    U32 xoffset, yoffset;
    U32 xres, yres;
    double xscale, yscale;
    double source_gamma;
    U32 chromaticities[8];      /* Fixed point x 100000 */
    int resolution_unit;        /* Units as in PNG */
    int offset_unit, scale_unit;
    int samples_per_pixel;
    int bits_per_sample;
    int significant_bits[4];
    int background_color[4];
    int is_color, has_alpha, has_trns;
    int is_interlaced, is_palette;
    int palette_size;
    U8 palette[3 * 256];
    U16 trans_values[3];
    U8 palette_trans_bytes[256];
    char *keywords[N_KEYWORDS];
    char *pixel_data_file;      /* Where to find the pixels */
    U32 png_data_size;
    char *png_data_file;    /* Untranslatable PNG chunks */
#define IMG_SIZE (sizeof (struct _image_info))
extern char *keyword_table[N_KEYWORDS];
extern U16 ASCII_tags[N_KEYWORDS];
/* Local ASSERT macro.  Assumes the function Assert() is defined somewhere in
 * the calling program (in this case, it's in ptot.c). */
#ifndef NDEBUG
#  define ASSERT(x) ((x)?(void)0:Assert(__FILE__,__LINE__))
#  define TRACE_STR(x) (fprintf(stderr,"TR: %s\n",(x)),\

#  define TRACE_INT(x) (fprintf(stderr,"TR: %ld\n",(long)(x)),\
#  define ASSERT(x)
#  define TRACE_STR(x)
#  define TRACE_INT(x)
/* Prototypes */
U32 update_crc(U32, U8 *, U32);
int main(int argc, char *argv[]);
void print_warning(int);
void error_exit(int);
void Assert(char *, int);
int read_PNG(FILE *, IMG_INFO *);
int get_chunk_header(void);
U32 get_chunk_data(U32);
int verify_chunk_crc(void);
int decode_IDAT(void);
U8 fill_buf(void);
void flush_window(U32);
int decode_text(void);
int copy_unknown_chunk_data(void);
size_t new_line_size(IMG_INFO *, int, int);
int get_local_byte_order(void);
int write_TIFF(FILE *, IMG_INFO *);
int create_tempfile(int); 
int open_tempfile(int);
void close_all_tempfiles(void);
void remove_all_tempfiles(void);
/* Interface to Mark Adler's inflate.c */
int inflate(void);
typedef unsigned char uch;
typedef unsigned short ush;
typedef unsigned long ulg;
typedef void *voidp;
#define slide (ps.inflate_window)
#define WSIZE ((size_t)(ps.inflate_window_size))
#define NEXTBYTE ((--ps.bytes_in_buf>=0)?(*ps.bufp++):fill_buf())
#define FLUSH(n) flush_window(n)
#define memzero(a,s) memset((a),0,(s))
#define qflag 1
/* A state structure is used to store all needed info about the reading process
 * so that we don't have to pass 4 or 5 arguments to every function in ptot.c. 
 * This is also used to share data with inflate.c. */
#define IOBUF_SIZE 8192 /* Must be at least 768 for PLTE */
typedef struct _png_state {
    FILE *inf, *tf[7];
    char *tfnames[7];
    IMG_INFO *image;
    U8 *buf, *bufp;
    U32 crc, bytes_remaining;
    U32 inflated_chunk_size;
    U32 current_chunk_name;
    S32 bytes_in_buf;       /* Must be signed! */
    U32 inflate_window_size;
    U8 *inflate_window;
    U16 inflate_flags;
    U16 sum1, sum2;
    U8 *last_line, *this_line;
    size_t byte_offset;
    size_t line_size, line_x;
    int interlace_pass;
    U32 current_row, current_col;
    int cur_filter;
    int got_first_chunk;
    int got_first_idat;

Listing Two

/* ptot.c -- Convert PNG (Portable Network Graphic) file to TIFF). Takes a
 * filename argument on the command line.
 * HISTORY: 95-03-10 Created by Lee Daniel Crocker <[email protected]>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include "ptot.h"
#include "errors.h"
#include "errors.h"
PNG_STATE ps = {0}; /* Referenced by tempfile.c, etc. */
char *keyword_table[N_KEYWORDS] = {
      "Author", "Copyright", "Software", "Source", "Title"
/* Local definitions and statics */
static int decode_chunk(void);
static int decode_IHDR(void);
static int decode_PLTE(void);
static int decode_gAMA(void);
static int decode_tRNS(void);
static int decode_cHRM(void);
static int decode_pHYs(void);
static int decode_oFFs(void);
static int decode_sCAL(void);
static int skip_chunk_data(void);
static int validate_image(IMG_INFO *);
/* Main for PTOT.  Get filename from command line, massage the
 * extensions as necessary, and call the read/write routines. */
    int argc,
    char *argv[])
    int err;
    FILE *fp;
    char *cp, infname[FILENAME_MAX], outfname[FILENAME_MAX];
    IMG_INFO *image;
    image = (IMG_INFO *)malloc((size_t)IMG_SIZE);
    if (NULL == image) error_exit(ERR_MEMORY);
    if (argc < 2) error_exit(ERR_USAGE);
    strcpy(infname, argv[1]);
    strcpy(outfname, argv[1]);
    if (NULL == (cp = strrchr(outfname, '.'))) {
        strcat(infname, ".png");
    } else (*cp = '\0');
    strcat(outfname, ".tif");
    if (NULL == (fp = fopen(infname, "rb")))
    err = read_PNG(fp, image);
    if (0 != err) error_exit(err);
    if (NULL == (fp = fopen(outfname, "wb")))
    err = write_TIFF(fp, image);
    if (0 != err) error_exit(err);
    return 0;
/* Print warning, but continue.  A bad code should never be
 * passed here, so that causes an assertion failure and exit. */
    int code)
    ASSERT(code >= 0 && code < PTOT_NMESSAGES);
    fprintf(stderr, "WARNING: %s.\n", ptot_error_messages[code]);
/* Print fatal error and exit. */
    int code)
    int msgindex;
    if (code < 0 || code >= PTOT_NMESSAGES) msgindex = 0;
    else msgindex = code;
    fprintf(stderr, "ERROR: %s.\n",
    if (0 == code) exit(1);
    else exit(code);
    char *filename,
    int lineno)
    fprintf(stderr, "ASSERTION FAILURE: "
     "Line %d of file \"%s\".\n", lineno, filename);
/* PNG-specific code begins here. read_PNG() reads the PNG file into the 
 * passed IMG_INFO struct. Returns 0 on success. */
    FILE *inf,
    IMG_INFO *image)
    int err;
    ASSERT(NULL != inf);
    ASSERT(NULL != image);
    memset(image, 0, IMG_SIZE);
    memset(&ps, 0, sizeof ps);
    ps.inf = inf;
    ps.image = image;
    if (NULL == (ps.buf = (U8 *)malloc(IOBUF_SIZE)))
    return ERR_MEMORY;
    /* Skip signature and possible MacBinary header; verify signature.
     * A more robust implementation might search for file signature 
     * anywhere in first 1k bytes or so, but in practice, the method 
     * shown is adequate or file I/O applications. */
    fread(ps.buf, 1, 8, inf);
    ps.buf[8] = '\0';
    if (0 != memcmp(ps.buf, PNG_Signature, 8)) {
    fread(ps.buf, 1, 128, inf);
    ps.buf[128] = '\0';
    if (0 != memcmp(ps.buf+120, PNG_Signature, 8)) {
       err = ERR_BAD_PNG;
       goto err_out;
    ps.got_first_chunk = ps.got_first_idat = FALSE;
    do {
    if (0 != (err = get_chunk_header())) goto err_out;
    if (0 != (err = decode_chunk())) goto err_out;
    /* IHDR must be the first chunk. */
    if (!ps.got_first_chunk &&
       (PNG_CN_IHDR != ps.current_chunk_name))
    ps.got_first_chunk = TRUE;
    /* Extra unused bytes in chunk? */
        if (0 != ps.bytes_remaining) {
       if (0 != (err = skip_chunk_data())) goto err_out;
        if (0 != (err = verify_chunk_crc())) goto err_out;
    } while (PNG_CN_IEND != ps.current_chunk_name);
    if (!ps.got_first_idat) {
     err = ERR_NO_IDAT;
     goto err_out;
    if (0 != (err = validate_image(image))) goto err_out;
    ASSERT(0 == ps.bytes_remaining);
    if (EOF != getc(inf)) print_warning(WARN_EXTRA_BYTES);
    err = 0;
    ASSERT(NULL != ps.buf);
    return err;
/* decode_chunk() is just a dispatcher, shunting the work of decoding incoming
 * chunk (whose header we have just read) to the appropriate handler. */
static int
    /* Every case in the switch below should set err. We set it 
     * here to gurantee that we hear about it if we don't. */
    int err = ERR_ASSERT;
    switch (ps.current_chunk_name) {
    case PNG_CN_IHDR:   err = decode_IHDR();    break;
    case PNG_CN_gAMA:   err = decode_gAMA();    break;
    case PNG_CN_IDAT:   err = decode_IDAT();    break;
    /* PNG allows a suggested colormap for 24-bit images. TIFF 
     * does not, and PLTE is not copy-safe, so we discard it. */
    case PNG_CN_PLTE:
        if (ps.image->is_palette) err = decode_PLTE();
        else err = skip_chunk_data();
    case PNG_CN_tRNS:   err = decode_tRNS();    break;
    case PNG_CN_cHRM:   err = decode_cHRM();    break;
    case PNG_CN_pHYs:   err = decode_pHYs();    break;
    case PNG_CN_oFFs:   err = decode_oFFs();    break;
    case PNG_CN_sCAL:   err = decode_sCAL();    break;
    case PNG_CN_tEXt:   err = decode_text();    break;
    case PNG_CN_zTXt:   err = decode_text();    break;
    case PNG_CN_tIME:   /* Will be recreated */
    case PNG_CN_hIST:   /* Not safe to copy */
    case PNG_CN_bKGD:
        err = skip_chunk_data();
    case PNG_CN_IEND:   /* We're done */
    err = 0;
    /* Note: sBIT does not have the "copy-safe" bit set, but that really only
    * applies to unknown chunks. We know what it is just like PLTE and that it
    * is probably safe to put in the output file. hIST and bKGD aren't 
    * (modifications to the output file might invalidate them), so we leave 
    * them out. */
    case PNG_CN_sBIT:
        err = copy_unknown_chunk_data();
        if (0 == (ps.current_chunk_name & PNG_CF_CopySafe))
          err = skip_chunk_data();
        else err = copy_unknown_chunk_data();
    return err;
/* get_chunk_header() reads the first 8 bytes of each chunk, which include the
 * length and ID fields. Returns 0 on success. The crc argument is 
 * preconditioned and then updated with the chunk name read. */
    int byte;
    ASSERT(NULL != ps.inf);
    ASSERT(NULL != ps.buf);
    if (8 != fread(ps.buf, 1, 8, ps.inf)) return ERR_READ;
    ps.bytes_remaining = BE_GET32(ps.buf);
    ps.current_chunk_name= BE_GET32(ps.buf+4);
    ps.bytes_in_buf = 0;
    if (ps.bytes_remaining > PNG_MaxChunkLength)
    for (byte = 4; byte < 8; ++byte)
      if (!isalpha(ps.buf[byte])) return ERR_BAD_PNG;
    ps.crc = update_crc(0xFFFFFFFFL, ps.buf+4, 4);
    return 0;
/* get_chunk_data() reads chunk data into the buffer, returning number of bytes
 * actually read. Do not use this for IDAT chunks; they are dealt with 
 * specially by the fill_buf() function. */
    U32 bytes_requested)
    ASSERT(NULL != ps.inf);
    ASSERT(NULL != ps.buf);
    ps.bytes_in_buf = (U32)fread(ps.buf, 1,
      (size_t)min(IOBUF_SIZE, bytes_requested), ps.inf);
    ASSERT((S32)(ps.bytes_remaining) >= ps.bytes_in_buf);
    ps.bytes_remaining -= ps.bytes_in_buf;
    ps.crc = update_crc(ps.crc, ps.buf, ps.bytes_in_buf);
    return ps.bytes_in_buf;
/* Assuming we have read a chunk header and all the chunk data, now check to 
 * see that CRC stored at end of the chunk matches the one we've calculated. */
    ASSERT(NULL != ps.inf);
    ASSERT(NULL != ps.buf);
    if (4 != fread(ps.buf, 1, 4, ps.inf)) return ERR_READ;
    if ((ps.crc ^ 0xFFFFFFFFL) != BE_GET32(ps.buf)) {
    return 0;
/* Read and decode IHDR. Errors that would probably cause the IDAT reader to 
 * fail are returned as errors; less serious errors generate a warning but 
 * continue anyway. */
static int
    ASSERT(NULL != ps.inf);
    ASSERT(NULL != ps.buf);
    ASSERT(NULL != ps.image);
    if (ps.bytes_remaining < 13) return ERR_BAD_PNG;
    if (13 != get_chunk_data(13)) return ERR_READ;
    ps.image->width = BE_GET32(ps.buf);
    ps.image->height = BE_GET32(ps.buf+4);
    if (0 != ps.buf[10] || 0 != ps.buf[11])
      return ERR_BAD_PNG;   /* Compression & filter type */
    ps.image->is_interlaced = ps.buf[12];
    if (!(0 == ps.image->is_interlaced ||
      1 == ps.image->is_interlaced)) return ERR_BAD_PNG;
    ps.image->is_color = (0 != (ps.buf[9] & PNG_CB_Color));
    ps.image->is_palette = (0 != (ps.buf[9] & PNG_CB_Palette));
    ps.image->has_alpha = (0 != (ps.buf[9] & PNG_CB_Alpha));
    ps.image->samples_per_pixel = 1;
    if (ps.image->is_color && !ps.image->is_palette)
        ps.image->samples_per_pixel = 3;
    if (ps.image->has_alpha) ++ps.image->samples_per_pixel;
    if (ps.image->is_palette && ps.image->has_alpha)
    /* Check for invalid bit depths. If a bitdepth is not one we can read,
    * abort processing. If we can read it, but it is illegal, issue a 
    * warning and continue anyway. */
    ps.image->bits_per_sample = ps.buf[8];
    if (!(1 == ps.buf[8] || 2 == ps.buf[8] || 4 == ps.buf[8] ||
      8 == ps.buf[8] || 16 == ps.buf[8])) return ERR_BAD_PNG;
    if ((ps.buf[8] > 8) && ps.image->is_palette)
    if ((ps.buf[8] < 8) && (2 == ps.buf[9] || 4 == ps.buf[9] ||
      6 == ps.buf[9])) return ERR_BAD_PNG;
    return 0;
/* Decode gAMA chunk. */
static int
    ASSERT(NULL != ps.inf);
    ASSERT(NULL != ps.buf);
    ASSERT(NULL != ps.image);
    if (0 != ps.image->palette_size)
    if (ps.bytes_remaining < 4) return ERR_BAD_PNG;
    if (4 != get_chunk_data(4)) return ERR_READ;
    ps.image->source_gamma = (double)BE_GET32(ps.buf) / 100000.0;
    return 0;
/* Decode PLTE chunk. Number of entries is determined by chunk length. A 
 * non-multiple of 3 is technically an error; issue a warning in that case.
 * IOBUF_SIZE must be 768 or greater, so we check that at compile time here. */
#if (IOBUF_SIZE < 768)
#  error "IOBUF_SIZE must be >= 768"
static int
    U32 bytes_read;
    ASSERT(NULL != ps.inf);
    ASSERT(NULL != ps.buf);
    ASSERT(NULL != ps.image);
    if (!ps.image->is_color) print_warning(WARN_PLTE_GRAY);
    if (0 != ps.image->palette_size) {
     return skip_chunk_data();
    ps.image->palette_size =
    min(256, (int)(ps.bytes_remaining / 3));
    if (0 == ps.image->palette_size) return ERR_BAD_PNG;
    bytes_read = get_chunk_data(3 * ps.image->palette_size);
    if (bytes_read < (U32)(3 * ps.image->palette_size))
      return ERR_READ;
    memcpy(ps.image->palette, ps.buf, 3 * ps.image->palette_size);
    ASSERT(0 != ps.image->palette_size);
    return 0;
/* Copy transparency data into structure. We will later expand the 
 * TIFF data into full alpha to account for its lack of this data. */
static int
    int i;
    U32 bytes_read;
    ASSERT(NULL != ps.inf);
    ASSERT(NULL != ps.buf);
    ASSERT(NULL != ps.image);
    if (ps.image->has_trns) print_warning(WARN_MULTI_TRNS);
    ps.image->has_trns = TRUE;
    if (ps.image->is_palette) {
        if (0 == ps.image->palette_size) {
    bytes_read = get_chunk_data(ps.bytes_remaining);
      ps.buf, (size_t)bytes_read);
    for (i = bytes_read; i < ps.image->palette_size; ++i)
      ps.image->palette_trans_bytes[i] = 255;
    } else if (ps.image->is_color) {
    if (ps.bytes_remaining < 6) return ERR_BAD_PNG;
        bytes_read = get_chunk_data(6);
    for (i = 0; i < 3; ++i)
       ps.image->trans_values[i] = BE_GET16(ps.buf + 2 * i);
    } else {
    if (ps.bytes_remaining < 2) return ERR_BAD_PNG;
        ps.image->trans_values[0] = BE_GET16(ps.buf);
    return 0;
static int
    int i;
    ASSERT(NULL != ps.inf);
    ASSERT(NULL != ps.buf);
    ASSERT(NULL != ps.image);
    if (ps.bytes_remaining < 32) return ERR_BAD_PNG;
    if (32 != get_chunk_data(32)) return ERR_READ;
    for (i = 0; i < 8; ++i)
       ps.image->chromaticities[i] = BE_GET32(ps.buf + 4 * i);
    return 0;
static int
    ASSERT(NULL != ps.inf);
    ASSERT(NULL != ps.buf);
    ASSERT(NULL != ps.image);
    if (ps.bytes_remaining < 9) return ERR_BAD_PNG;
    if (9 != get_chunk_data(9)) return ERR_READ;
    ps.image->resolution_unit = ps.buf[8];
    if (ps.buf[8] > PNG_MU_Meter) print_warning(WARN_BAD_VAL);
    ps.image->xres = BE_GET32(ps.buf);
    ps.image->yres = BE_GET32(ps.buf + 4);
    return 0;
static int
    ASSERT(NULL != ps.inf);
    ASSERT(NULL != ps.buf);
    ASSERT(NULL != ps.image);
    if (ps.bytes_remaining < 9) return ERR_BAD_PNG;
    if (9 != get_chunk_data(9)) return ERR_READ;
    ps.image->offset_unit = ps.buf[8];
    if (ps.buf[8] > PNG_MU_Micrometer) print_warning(WARN_BAD_VAL);
    ps.image->xoffset = BE_GET32(ps.buf);
    ps.image->yoffset = BE_GET32(ps.buf + 4);
    return 0;
/* Decode sCAL chunk. Note: as of this writing, this is not an official PNG 
 * chunk. It probably will be by the time you read this, but it might possibly
 * change in some way. You have been warned. It also has no TIFF equivalent, so
 * this only gets read into the structure. */
static int
    ASSERT(NULL != ps.inf);
    ASSERT(NULL != ps.buf);
    ASSERT(NULL != ps.image);
    if (ps.bytes_in_buf == IOBUF_SIZE) {
    ps.buf[ps.bytes_in_buf] = '\0';
    ps.image->scale_unit = ps.buf[0];
     if (ps.buf[0] < PNG_MU_Meter || ps.buf[0] > PNG_MU_Radian)
    ps.image->xscale = atof(ps.buf+1);
    ps.image->yscale = atof(ps.buf + (strlen(ps.buf+1)) + 2);
    return 0;
/* Skip all remaining data in current chunk. */
static int
    U32 bytes_read;
    do {
    bytes_read = get_chunk_data(ps.bytes_remaining);
    } while (0 != bytes_read);
    return 0;
/* Ensure that the image structure we have created by reading the input PNG is
 * compatible with whatever we intend to do with it. In this case, TIFF can 
 * handle anything, so we use this as a sanity check on basic assumptions. */
static int
    IMG_INFO *image)
    if (0 == image->width || 0 == image->height)
    return ERR_BAD_IMAGE;
    if (image->samples_per_pixel < 1 ||
    image->samples_per_pixel > 4) return ERR_BAD_IMAGE;
    if (image->is_palette && (image->palette_size < 1 ||
        image->palette_size > 256)) return ERR_BAD_IMAGE;
    if (NULL == image->pixel_data_file) return ERR_BAD_IMAGE;
    return 0;
/* End of ptot.c. */

Copyright © 1995, Dr. Dobb's Journal

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.