This article contains the following executables: TIFF.ZIP TIFFIMAGE.ZIP
Anthony Meadow, Rocky Offner, and Michael Budiansky form the development nucleus of Bear River Associates Inc., P.O. Box 1900, Berkeley, CA 94701.
It may sound categorical, but it's true: Anyone who is writing software that works with bit-mapped images should support the Tag Image File Format (TIFF) Why? Simply because TIFF has become the industry-standard format for storing bit-mapped images; the format is supported by almost all the desktop-publishing and scanner software in both the Macintosh and MS-DOS communities.
TIFF came into existence through the cooperation of several companies, especially Microsoft and Aldus. Many other companies have joined in the effort to develop and support this file format, including most of the scanner manufacturers (such as DEST Corp., Datacopy, and Hewlett-Packard) and the developers of desk-top publishing software (Letraset, PS Publishing, and so on). The most recent version is Revision 4, released on April 31, 1987. Revision 5 is now under discussion and adds several important features.
The primary reason for the success of TIFF is the rapid rise of desktop publishing (DTP). DTP software and scanners became so popular that a way was needed to move images from scanners to publishing software. Initially, every scanner manufacturer developed a proprietary format for storing images, but it quickly became obvious that there was a better way to solve this problem. With the support of Aldus and Microsoft. TIFF became the alternative to supporting an odd collection of proprietary file formats. Fortunately, almost all scanner manufacturers decided to support TIFF, which has encouraged almost all developers of desktop-publishing software and paint programs to adopt TIFF, too.
Later in this article, we will describe the TIFF Library Package, which is now in the public domain. The package includes routines for reading and writing TIFF files, a sample application that uses them, and a utility program for examining TIFF files. Also included is a set of TIFF files that most TIFF implementations should be able to read. This set of files was designed to serve as an initial validation suite for TIFF implementations. You can write or call for the full package--the details are at the end of this article.
TIFF has satisfied many developers because it is capable of storing all the details about an image, which is not surprising because TIFF was developed as a superset of several existing proprietary formats.
TIFF is unlike most other file formats in that most information is not stored in fixed locations in the file. There are only 8 bytes of information in a TIFF file that have a specified location--the first 8 bytes in the file. Everything else is reached by using offsets from the start of the file. The categories of information that are currently supported seem to be sufficient for almost all applications today. If these categories are not sufficient, then others can be easily added. In fact, even proprietary information can be stored in a TIFF file without violating the specification.
Bit maps of any kind can be stored in a TIFF file---bilevel, gray-scale, and color are all supported in Revision 5 of the TIFF standard. Images of any resolution, size, number of samples per pixel, and so on can be stored in TIFF files. These images might come from any kind of device, including scanners, facsimile machines, video frame stores, and so on, or from any kind of software that can create a bit-mapped image.
TIFF is not supposed to be the only graphics file format. It may not provide sufficient capabilities for all bit-mapped graphics applications, although it's difficult to imagine what the exceptions might be. It does not provide support for object-oriented graphics, whereby images are composed of ellipses, rectangles, splines, and so on (as in Adobe Illustrator, The MacDraw, or AutoCAD). TIFF also does not support PostScript or any other page-description language. The PICT and PICT2 formats supported by the Macintosh provide much of the functionality of TIFF with respect to bit maps and much more functionality to support object-oriented graphics and PostScript, but these file formats are proprietary. Table 1, below, compares TIFF with these and other file formats.
TIFF file structure is defined in a specification that is available from Microsoft or with the TIFF Library Package. Revision 4 is the latest, but Revision 5 is now in the works and adds conformance levels, a compression method for color and gray-scale images, and support for a "palette" type of color. The obvious place to start learning about the TIFF file structure is to read the specification. After that, try dumping several TIFF files with td, the dump utility provided as part of the TIFF Library Package. Look at a variety of files in order to see what you can do with this format. The TIFF Library Package includes about two dozen files that show a wide variety of legal TIFF files.
Table 1: Comparison of image file formats.
Figure 2: Structure of a TIFF file.
A TIFF file is composed of three kinds of elements: an image file header, one or more image field directories, and collections of data. Figure 1, page 27, shows the general structure of a TIFF file. The image file header (IFH) always occupies the first 8 bytes in a TIFF file and is the only element that has a fixed position within the file. The image file header contains the byte order flag, the file version number (currently 42), and an offset to the first image file directory (IFD).
A TIFF file is composed of three kinds of elements: an image file header, one or more image field directories, and collections of data. Figure 1, page 27, shows the general structure of a TIFF file. The image file header (IFH) always occupies the first 8 bytes in a TIFF file and is the only element that has a fixed position within the file. The image file header contains the byte order flag, the file version number (currently 42), and an offset to the first image file directory (IFD).
All elements other than the IFH have variable positions within the file and are located by using byte offsets from the start of file. A TIFF file is considered to be a continuous sequence of (8-bit) bytes numbered from 0. The largest a TIFF file can be is 232 bytes long, or about 4 Gbytes.
The TIFF specification, like most other specifications, is not especially easy to read. One misunderstanding that we've seen several times concerns the location of various components of the file. The first image file directory is not necessarily lust after the image file header---it might be anywhere in the file. Its location is given in the image file header.
A TIFF file may include multiple versions of an image---for example, it might be useful to provide a lower-resolution screen version of a 300 dpi image (to present on a screen), especially if an optimized scaling algorithm was used to generate the lower-resolution version. Of course, an application would have to look for the lower-resolution version and display it rather than generate one on the fly from the 300-dpi version. As far as we know, there are no applications that write more than one version of an image to a file. perhaps because of the additional disk space required. At any rate, if there are multiple versions of an image, then each version is described by the information in its image file directory. Each IFD contains an offset to the next IFD or 0 if there isn't another one.
Before we look at what an IFD contains, let's look briefly at tags and tag entries. A tag entry is basically a chunk of data (or a field) with a name (or tag). A tag entry might point to the image, the image height, the image width, or the orientation of the image.
An image file directory contains all the tag entries for a version of an image, ordered by tag type. Figure 2 page 32, shows what an IFD looks like. Only one tag of each tag type is allowed in an IFD.
As shown in Figure 3, below, each tag is 12 bytes long, where the first 2 bytes are the tag type. There are almost 40 tag types, which are defined in the specification. You can use other tags, but you should contact the TIFF administrator at Microsoft first. It's important to reserve any nonspecified tags so that there won't be any conflicts with files created by other applications. The address of the administrator is given at the end of this article.
The next 2 bytes in the tag entry are the data type, and the next 4 bytes are the length (or count). Five data types are defined in the specification: byte (1-byte unsigned integer). ASCII, short (2-byte unsigned integer), long (4-byte unsigned integer), and rational (two longs---the first is the numerator of a fraction and the second is the denominator). The length field in the tag entry gives the length of the data for this tag in terms of the data type---for example, if the data has a length of 1 and is of type long, then the data is 4 bytes long. By using the information in these two fields, you know exactly how much data there is for this tag.
The last 4 bytes in the tag entry are either an offset to the data or, if the data occupies 4 bytes or less, the data itself. If the data is less than 4 bytes in size, then it is left-justified within the 4 bytes. This convention for the Offset field optimizes access to small chunks of information, although it does make the file structure more complex.
Images in TIFF files are usually divided into strips to allow for the memory limitations of most machines. A strip is typically an integral number of scan lines (a scan line is usually a horizontal row). Most applications cannot fit an entire image in memory at one time, for example, an 8-1/2 x 11-inch bilevel image at 300 X 300 dpi requires more than 1 Mbyte of space. Most Macintoshes don't have this much space available, and most PCs don't have more than 640K of memory to start with.
Even though it is legal to write an image as a single strip, virtually all applications write images as a set of strips, where one strip typically is less than 64K in size. This allows applications to work with images using less memory than would otherwise be needed. Although if enough memory is available, an application could still read a complete image into memory. When an image is divided into strips, the strips are either all compressed or all not compressed. The TIFF specification does allow you to compress some strips but not all.
The StripOffsets tag entry, as the name implies, contains offsets to the image data and not to the image. Each offset points to one image strip. This is true whether the image is composed of a single strip or many strips. These offsets are the only way to get to the image.
Gray-scale images are stored with all the bits for each pixel picked contiguously. Color images can be stored with all bits per pixel packed together or with the bits describing each color (in the RGB color model) stored in separate planes. The tag SamplesPerPixel has a value of 1 for a bilevel or gray-scale image, but for a color picture with three planes, it will have a value of 3. The Planar-Configuration tag tells you whether there is one image plane or several---for example, if a color image was stored as multiple planes, there is one plane for red, a second for green, and a third for blue.
Two other tags always present in TIFF files are Imagewidth and ImageLength; they contain the width and height of the image in pixels. Each of these is usually 2 bytes long (although a program should always check the Data Type field in the tag entry to find out how long a field really is). For these two tags, the data is actually kept in the Offset field of the tag entry, rather than being pointed to by a file offset.
Figure 2: Structure of an image file directory.
Figure 3: Structure of a tag entry
Image files are large compared with the average word-processing or spreadsheet document. Now that gray-scale scanners are on the market (and color scanners can't be too far away either), image files could become much larger. Table 2, this page, gives the size of various images at different resolutions, ln order to conserve users' disk space applications should compress images.
Revision 4 of the TIFF standard specifies several compression schemes for bilevel images. Revision 5 adds a method for compression of color and gray-scale. (The compression methods defined in Revision 5 are listed in Table 3, below.) Each of these methods is lossless---that is, each preserves all information in the image. Compression methods are possible that produce much highest compression ratios, but they do not save all of the image's information. These methods could be supported in a TIFF file, but no one has seen the need for them yet. An application needs to support only one compression method when writing each type of image (bilevel, gray-scale, or color) but should be able to read an image in any of the other compression methods.
The default compression method in the specification isn't really compression; it's simply packing data into bytes as tightly as possible. One of the other methods is a variation on this, where the data is packed into (16-bit) words as tightly as possible.
The compression methods for bilevel images are derived from the CCITT (International Telegraph and Telephone Consultative Committee) standards developed for facsimile machines. These methods are based on a Huffman run-length code. The CCITT arrived at the code by looking at samples of typical documents sent by facsimile. It's quite likely that images used in desktop publishing are not like the documents used in developing the CCITT standards, so the compression methods are probably not optimal. They aren't too bad, however; a compression ratio of 4 to 1 is typical. The TIFF standard allows for additional compression schemes in the future, but the TIFF file I/O code will have to be revised in all applications so hat they can read files that use these new compression methods.
The most obvious reason to use TIFF is that everyone well, almost everyone, already does. If you are writing an application that works with bit-mapped images, it can work with all the existing applications that already produce or read TIFF files. If you develop a proprietary file format, you will have to talk many others into supporting it.
Another advantage of TIFF is that it was designed to support changes easily. By using new tags, additional information can be added to TIFF files. Older applications won't be able to take advantage of the information in the new tags, but as long as the information they need is there, they can still use the file. Hopefully you won't require any new tags because TIFF already provides a rich set. The kind of information that TIFF supports is a superset of virtually all that contained in proprietary image file formats.
It's also possible to store proprietary information in a TIFF file. The tags numbered 32,768 to 65,535 are reserved for this purpose. Developers who would like one or more tags reserved should contact the TIFF administrator at Microsoft. Obviously, only applications "in the know" are able to use such proprietary information.
Portability of data is becoming more and more important these days. It's now common to see Macs and PCs connected over a local-area network. TIFF supports portability of data because both the Motorola and Intel differences are clearly defined and can therefore be handled easily by an application. TIFF files can also be easily moved to almost any other file system because TIFF makes no assumptions about the underlying file system.
Resolution* Bits/Pixel
(dpi) 1 4 8 12 24
72 61 242 485 727 1,454
150 263 1,052 2,104 3,156 6,311
300 1,052 4,208 8,415 12,623 25,245
600 4,208 16,830 33,660 50,490 100,980
1,200 16,830 67,320 134,640 201,960 403,920
* Assumes that all images are 8.5 x 11 inches in size
** Assumes that the horizontal and vertical resolution are the same
Tag Value Name
1 None (but pack data into bytes tightly)
2 CCITT Group 3 1-Dimensional modified Huffman run length encoding
3 Facsimile-compatible CCITT Group 3
4 Facsimile-compatible CCITT Group 4
5 Differential run-length encoding
32771 None (same as 1 except pack tightly into words)
32773 PackBits compression
TIFF does have a few problems, but as the standard evolves, many of them are being solved.
The TIFF file structure is not simple---it is more complex than many existing proprietary file formats, such as MacPaint's. This complexity costs time in several ways for example, there is more overhead to write a TIFF file than a file with a simpler format. It also takes longer to write the TIFF I/O functions for an application because of TIFF's generality, although the library described in this article will reduce this development time for Macintosh developers. There are also TIFF toolkits available from others for MSDOS machines.
The richness of the file structure has caused a couple of other problems. The TIFF standard (until Revision 5) did not specify a minimal set of tags, so each developer has used a different subset of tags in his or her files. This problem should be solved in Revision 5 of the standard, which specifies six conformance levels. Each level is specified by listing which tags and compression methods must be supported. Hopefully all developers will make the (few) changes in future revisions of their products to bring them to a reasonable conformance level.
Table 4, page 39, outlines the features of the various conformance levels. The FAX conformance level is in a different category from the others because the features required to support facsimile machines are special. An application that supports level 3 and FAX is described as conforming to level 3 + FAX.
Another problem is that there are no compression schemes for color and gray-scale images. An 8.5 x 11 inch gray-scale image with 8 bits per pixel at 300-dpi resolution requires 80 Mbytes of storage without compression. Even people with hard disks would quickly run out of room without some form of compression for these images. This problem is also being solved in Revision 5 of the OFF standard, which details a compression method for both color and gray-scale.
Necessity is the mother of invention. The TIFF Library Package was created as part of DEST Corp.'s product Publish Pac, which is a Macintosh application that lets users operate one of DEST's scanners to read in images and text. (There is also a version of Publish Pac for the IBM PC.) DEST was an early adopter of TIFF; it decided to use TIFF as its standard file format rather than develop yet another proprietary format. The TIFF Library Package is used by Publish Pac to read and write TIFF files. In its current state, it is used in the latest version of Publish Pac. It is written in MPW C (from Apple's Macintosh Programmer's Workshop) and could be ported to other versions of C on the Macintosh with little trouble.
Table 4: Conformance levels in TIFF,Revision 5
The TIFF Library Package provides low-level routines for working with TIFF files. These routines provide a standard way to read and write TIFF files as well as to manage TIFF tags. The TIFF file format requires a certain amount of bookkeeping, such as ordering all tags sequentially. The library routines handle this automatically to ensure that all images are consistent with the specification. If you use this library, you won't have to learn all the low-level details of what TIFF files look like, but you will still have to decide which tags you want to read and write. The library includes routines to read and write the TIFF header, read and write the tags as a group, and read and write images. Table 5, page 43, contains a list of all the function names.
Reading and writing the file header is straightforward. The two routines TReadHeader and TWriteHeader read and write the OFF file image file directory. If the byte ordering is different from the native ordering, the offset to the directory and all subsequent numerical values are adjusted before they are returned, so an application doesn't have to know whether the file originated on a Macintosh or a PC.
To facilitate tag handling, an in-memory, tag-management scheme was designed so that all tags and their values are read from the file into memory. From there the tags and their values can easily be located, modified, and removed, and new tags can be inserted with simple function calls. When reading a TIFF file, call TReadTags to get all the tags into memory at once. You can then make function calls to check for the presence of a tag or get a tag's value.
When you're ready to create a TIFF file, make calls to create or modify the appropriate tags. The position of the tags and the method for storing the values according to the TIFF specification is handled by the library routines. A call to TPutPtrTag or TPutHdlTag is made for each tag that's being added to the TIFF file, with two exceptions. The StripOffsets and StripBytesCounts tags are created and updated automatically by the routine that writes the image strips to the file. Calls to TwriteTags include a pointer to the tag's value, and the tag is included in the in-memory tag list.
To write an image to a file, an application makes one call to TwriteImageStrip for each strip that will be in the TIFF file. Each strip must have a number of rows less than or equal to that specified by the RowsPerStrip tag. Reading an image from a file is a bit more flexible. Any number of rows can be requested by using the TReadImage function starting with any row, regardless of the number of rows per strip.
Two auxiliary routines, TFixOddRowBytes and TunftxOddRowBytes, provide image adjustment. Using compression method 1, the bits of each row of an image are stored in the smallest possible number of bytes. Some systems normally fit the bits into the smallest number of (16 bit) words---for example, in the Macintosh operating system, bit maps are always an even number of bytes in length. These auxiliary routines are provided to translate the image between these two storage methods if the bits in a row of an image can fit in an odd number of bytes.
The current TIFF Library Package supports most of the first and second conformance levels. Only compression methods 1 (tight packing into bytes 1 and 2) modified Huffman run-length encoding) are supported at this time. Also, only the default orientation is supported, where the 0th row represents the top of the image and the 0th column represents the left side of the image there (are seven other possible orientations). When Revision 5 of the TIFF specification is released, the package will probably be upgraded to meet conformance level 3.
The sample program demonstrates how to use the routines in the Macintosh version of the TIFF Library Package. The program is limited in its ability to display images or read in complex nonbilevel TIFF images, but it does use most of the functions in the library. It can read and write TIFF files and will let you cut and paste Macintosh PICT images to and film the Clipboard---for example, a drawing can be made in MacPaint, cut or copied into the Clipboard, and then pasted into the sample program and written out to a TIFF file. Similarly, a TIFF image can be read into the program, copied into the Clipboard, and pasted into other programs such as MacPaint.
Listing One, page 54, shows two functions---ReadTiff and WriteTiff---from the sample program. We will now use them to demonstrate the use of the library functions.
The ReadTiff function shows how to use the TIFF library routines to read a TIFF file. First, you read the file header using TReadHeader, and then you read all the tags into memory using the TReadTags function.
Subsequently, local data structures are filled with the values of the tags via calls to the tag-management routines TFindTag and TGetTag. If a tag is present, then the value is set; if not and the tag has a default value, the local data for that item is set to the default. If there is no default for a tag, then you either ignore that value or report an error if the tag is necessary.
Once the tag values have been obtained, you determine if there are any values that require facilities beyond those provided to display this image. If so, an error is reported. Once you've determined that the image as described by the tags is correct and that you are able to display that image, a call to TReadImage is made. The sample program requests the lines of the image from line 0 through the number of rows in the entire image or the number of rows that will fit into 32K of memory, whichever is smaller. If you success fully read the image, then it (or some portion of it) is displayed in a simple window.
The WriteTiff function demonstrates how to write a TIFF file using functions from the TIFF library. A subset of the available tags is placed in the tag list using the TPutPtrTag function. The tags specified are those required by the specification plus a couple of others that, although not required by the TIFF specification, are quite useful.
File-handling functions
TReadHeader
TwriteHeader
TReadTags
TWriteTags
TReadImage
TwriteImageStrip
Tag list management functions
TFindTag
TGetTag
TPutPtrTag
TPutHdlTag
Auxiliary functions
TfixOddRowBytes
TUnfixOddPowBytes
After placing all the tags you want in the tag list, the strips are written to the file using the TWriteImageScrip function. The number of rows per strip (8) used by the sample program is an arbitrary value chosen to demonstrate how to write multiple strips. The RowsPerStrip tag can be set to any value the creator desires, although it is a good idea not to let strips get too big. For the Macintosh, it is wise to limit strips to no more than 32K.
Once all the strips have been written, the tags are flushed to the file by a call to TwriteTags. Finally, the header is written using TwriteHeader. After the file has been closed and flushed to disk, you've created a TIFF file.
The TIFF file format has become popular in both the Macintosh and MS-DOS worlds. It offers a way to share any kind of bit-mapped image with a large and growing number of other applications. Anyone who is writing software that works with bit maps should support TIFF. The TIFF Library Package described in this article will help any Macintosh programmer read and write TIFF files with a minimum of work. It can also be ported to other machines.
Because of the size of the TIFF Library Package (close to 1 Mbyte), we've been forced to forego the usual distribution method of printing the listings in the magazine or putting them on CompuServe. Serious developers and readers of DDJ can, however, receive a free set of disks for the TIFF Library Package by mentioning this article in a request to one of two sources.
For a copy of the TIFF Library Package for the Macintosh, send your name and address to Bear River Associates Inc., Attn: TIFF, P.O. Box 1900, Berkeley, CA 94701 or call 415-644-9400 (9 A.M. to 5 P.M. PST) and ask for the TIFF Library Package.
A similar TIFF package is available for the PC. To get this package, write to DEST Corp., Attn.: Debra Levesque, 1201 Cadillac Ct., Milpitas, CA 95035 or call 408-946-7100 and ask for Debra Levesque.
To receive a copy of the latest version of the TIFF standard, to reserve a proprietary tag, or to comment on the standard, write to Manny Vellon, Windows Marketing Group, Microsoft Corp., 16011 N.E. 36th Wy., P.O. Box 97017, Redmond, WA 98073-9717.
Andrews, Nancy; and Fry, Stan. "TIFF: An Emerging Standard for Exchanging Digitized Graphics Images." Microsoft Systems Journal (July 1987): 71-76.
[LISTING ONE]
/* Primary Interface Files */
#include "Types.h"
#include "Quickdraw.h"
#include "Windows.h"
/* Other Interface files */
#include "Errors.h"
#include "Files.h"
#include "Memory.h"
#include "Packages.h"
#include "Scrap.h"
/* Application-specific Include files */
#include "::TiffLibrary:TIFFLib.h"
#include "sample.h"
#include "messages.h"
static Ptr SetIDPtr();
static void CleanUp();
static void InitID();
#define MAXIMAGESIZE 0x8000 /* Limit images to 32K for now */
#define INFINITY 0x4000000 /* TIFF Spec says 2**32-1 but this is big enough */
/* Read in an image from a TIFF format file. As the code demonstrates, we
* do not read in very complicated images. We read in a number of tags,
* and reject the image as an unsuitable tiff file if any of several
* conditions exist. We do not read in images that have more than one bit
* of image data per pixel. Of those simple images that we do read, we
* will only read the first 32k of that image.
*/
Boolean ReadTiff(refNum, myBitMapPtr)
Int16 refNum;
BitMap *myBitMapPtr;
{
Handle listH;
Boolean oddRowBytes;
Int8 dummy;
Int16 byteOrder,
rowBytes,
scrnHRes,
scrnVRes;
Int32 tagOffset,
nextDirOffset,
nextFileFree, /* next free location in output file */
dirOffset,
rowsPerImage,
count,
size;
Rational xRes,
yRes;
Rect imageRect;
TiffDirEntry tagDirEntry;
id id; /* image description */
InitID(&id);
ScreenRes(&scrnHRes, &scrnVRes); /* if needed for defaults */
/*
* Read in header and Tags
*/
if (TReadHeader(refNum, &dirOffset, &byteOrder) != noErr) {
ErrorMessage(BADREADHEADER);
return(false);
}
if(TReadTags(refNum, byteOrder,
&listH, dirOffset, &nextDirOffset) != noErr) {
ErrorMessage(BADREADTAGS);
return(false);
}
/* Get tags values.
*/
/* SUBFILE_TYPE_TAG */
if (TFindTag(listH, &tagOffset, SUBFILE_TYPE_TAG))
TGetTag(listH, tagOffset, &id.subfileType, sizeof(id.subfileType));
else {
ErrorMessage(BADTIFF);
CleanUp(listH, &id);
return(false);
}
/* IMAGE_WIDTH_TAG */
if (TFindTag(listH, &tagOffset, IMAGE_WIDTH_TAG))
TGetTag(listH, tagOffset, &id.imageWidth, sizeof(id.imageWidth));
else {
ErrorMessage(BADTIFF);
CleanUp(listH, &id);
return(false);
}
/* IMAGE_LENGTH_TAG */
if (TFindTag(listH, &tagOffset, IMAGE_LENGTH_TAG))
TGetTag(listH, tagOffset, &id.imageLength, sizeof(id.imageLength));
else {
ErrorMessage(BADTIFF);
CleanUp(listH, &id);
return(false);
}
/* ROWS_PER_STRIP_TAG */
if (TFindTag(listH, &tagOffset, ROWS_PER_STRIP_TAG)) {
TGetTag(listH, tagOffset, &id.rowsPerStrip, sizeof(id.rowsPerStrip));
tagDirEntry = GetDirEntry(listH, tagOffset);
switch(tagDirEntry.type) {
case LONG:
break;
case SHORT: /* ok, but convert returned value */
id.rowsPerStrip = (long)( *((Int16 *)(&id.rowsPerStrip)) );
break;
default:
ErrorMessage(BADTIFF);
CleanUp(listH, &id);
return(false);
}
}
else {
id.rowsPerStrip = INFINITY;
}
/* SAMPLES_PER_PIXEL_TAG */
if (TFindTag(listH, &tagOffset, SAMPLES_PER_PIXEL_TAG))
TGetTag(listH, tagOffset, &id.samplesPerPixel, sizeof(id.samplesPerPixel));
else {
id.samplesPerPixel = 1;
}
/* BITS_PER_SAMPLE_TAG */
id.bitsPerSample = SetIDPtr(listH, BITS_PER_SAMPLE_TAG, 1L, SHORT);
if (id.bitsPerSample == nil) {
CleanUp(listH, &id);
return(false);
}
/* PLANAR_CONFIG_TAG */
if (TFindTag(listH, &tagOffset, PLANAR_CONFIG_TAG))
TGetTag(listH, tagOffset, &id.planarConfig, sizeof(id.planarConfig));
else
id.planarConfig = 1;
/* COMPRESSION_TAG */
id.compression = SetIDPtr(listH, COMPRESSION_TAG, 1L, SHORT);
if (id.compression == nil) {
CleanUp(listH, &id);
return(false);
}
/* MIN_SAMPLE_VALUE_TAG */
id.minSampleValue = SetIDPtr(listH, MIN_SAMPLE_VALUE_TAG, 0L, SHORT);
if (id.minSampleValue == nil) {
CleanUp(listH, &id);
return(false);
}
/* MAX_SAMPLE_VALUE_TAG */
id.maxSampleValue = SetIDPtr(listH, MAX_SAMPLE_VALUE_TAG,
(Int32)((1 << *id.bitsPerSample) - 1), SHORT);
if (id.maxSampleValue == nil) {
CleanUp(listH, &id);
return(false);
}
/* PHOTOMETRIC_INTERP_TAG */
if (TFindTag(listH, &tagOffset, PHOTOMETRIC_INTERP_TAG))
TGetTag(listH, tagOffset, &id.photoInterp, sizeof(id.photoInterp));
else {
id.photoInterp = 0; /* assume mac photometric interpretation */
}
/* FILL_ORDER_TAG */
if (TFindTag(listH, &tagOffset, FILL_ORDER_TAG))
TGetTag(listH, tagOffset, &id.fillOrder, sizeof(id.fillOrder));
else
id.fillOrder = 1;
/* ORIENTATION_TAG */
if (TFindTag(listH, &tagOffset, ORIENTATION_TAG))
TGetTag(listH, tagOffset, &id.orientation, sizeof(id.orientation));
else
id.orientation = 1;
/* X_RESOLUTION_TAG */
if (TFindTag(listH, &tagOffset, X_RESOLUTION_TAG))
TGetTag(listH, tagOffset, &id.xResolution, sizeof(id.xResolution));
else {
id.xResolution.numerator = (Int32)scrnHRes;
id.xResolution.denominator = 1;
}
/* Y_RESOLUTION_TAG */
if (TFindTag(listH, &tagOffset, Y_RESOLUTION_TAG))
TGetTag(listH, tagOffset, &id.yResolution, sizeof(id.yResolution));
else {
id.yResolution.numerator = (Int32)scrnVRes;
id.yResolution.denominator = 1;
}
/* Initialize of non-tag values.
*/
oddRowBytes = (((id.imageWidth * (*id.bitsPerSample)) + 7) / 8) % 2 != 0;
id.stripsPerImage =
(id.imageLength + id.rowsPerStrip - 1) / id.rowsPerStrip;
/* Check Tag Values to see if we can read this TIFF file.
*
* NOTE: Although the majority of the tag were read, all the values
* obtained are not used in displaying the image in THIS PROGRAM.
* Those not used were read in simply to provide the example.
*/
if ( (id.samplesPerPixel != 1) ||
(*id.bitsPerSample != 1) ||
(id.planarConfig != 1 && id.planarConfig != 2) ) {
ErrorMessage(BADTIFF);
CleanUp(listH, &id);
return(false);
}
if (id.photoInterp != 0) /* We don't translate yet so let 'em know */
ErrorMessage(BADPHOTOINTERP);
rowBytes = (((id.imageWidth - 1) / (2 * 8)) + 1) * 2;
/* only make image as much as will fit in MAXIMAGESIZE for now */
size = id.imageLength * rowBytes;
if (size > MAXIMAGESIZE) {
ErrorMessage(IMAGECROPWARN);
size = MAXIMAGESIZE;
}
rowsPerImage = size / rowBytes;
/* Prepare bitmap.
*/
if (myBitMapPtr->baseAddr != nil)
DisposPtr(myBitMapPtr->baseAddr);
if ((myBitMapPtr->baseAddr = MyNewPtr(size)) == nil) {
CleanUp(listH, &id);
return(false);
}
myBitMapPtr->rowBytes = rowBytes;
myBitMapPtr->bounds.top = 0;
myBitMapPtr->bounds.left = 0;
myBitMapPtr->bounds.bottom = rowsPerImage;
myBitMapPtr->bounds.right = id.imageWidth;
/* Read in image.
*/
if (TReadImage(refNum, listH,
0L, myBitMapPtr->baseAddr, rowsPerImage, -1) != noErr) {
CleanUp(listH, &id);
return(false);
}
if (oddRowBytes)
TFixOddRowBytes(myBitMapPtr);
DisplayImage(FrontWindow(), myBitMapPtr);
CleanUp(listH, &id);
return(true);
}
void WriteTiff(refNum, myBitMapPtr)
Int16 refNum;
BitMap *myBitMapPtr;
{
Handle listH;
Ptr bufferPtr;
Boolean oddRowBytes;
Int8 dummy;
Int16 byteOrder,
subfileType,
imageWidth,
imageLength,
fillOrder,
compressType,
photoInterp,
bitsPerPixel,
minSampleValue,
maxSampleValue,
orientation,
tiffRowBytes, /* number of bytes per row in TIFF format */
plane, /* dummy parameter for TWriteImageStrip */
scrnHRes,
scrnVRes;
Int32 rowsPerStrip,
nextFileFree, /* next free location in output file */
startLine,
numLines,
dirOffset,
count;
Rational xRes,
yRes;
Rect imageRect;
/* get a handle for the in memory tag list */
listH = NewHandle(0);
if (MemError() != noErr) {
ErrorMessage(BADMEMORY);
return;
}
ScreenRes(&scrnHRes, &scrnVRes);
imageRect = myBitMapPtr->bounds;
/* write out 8 rows per strip - 8 is an arbitrary number */
rowsPerStrip = MIN(imageRect.bottom - imageRect.top, 8);
/* initialize tag values */
byteOrder = MOTOROLA;
subfileType = 1;
imageWidth = imageRect.right;
imageLength = imageRect.bottom;
bitsPerPixel = 1;
fillOrder = 1;
compressType = 1;
photoInterp = 0;
minSampleValue = 0;
maxSampleValue = (1 << bitsPerPixel) - 1;
orientation = 1;
xRes.numerator = (Int32)scrnHRes;
xRes.denominator = 1;
yRes.numerator = (Int32)scrnVRes;
yRes.denominator = 1;
tiffRowBytes = (imageRect.right * bitsPerPixel + 7) / 8;
oddRowBytes = (tiffRowBytes % 2) != 0;
/* Put tags in memory list.
* The order tags are put in the list with TPutPtrTag is NOT important.
*/
if ( TPutPtrTag(listH, SUBFILE_TYPE_TAG, SHORT,
1L, &subfileType) != noErr ||
TPutPtrTag(listH, IMAGE_WIDTH_TAG, SHORT,
1L, &imageWidth) != noErr ||
TPutPtrTag(listH, IMAGE_LENGTH_TAG, SHORT,
1L, &imageLength) != noErr ||
TPutPtrTag(listH, ROWS_PER_STRIP_TAG, LONG,
1L, &rowsPerStrip) != noErr ||
TPutPtrTag(listH, X_RESOLUTION_TAG, RATIONAL,
1L, &xRes) != noErr ||
TPutPtrTag(listH, Y_RESOLUTION_TAG, RATIONAL,
1L, &yRes) != noErr ||
TPutPtrTag(listH, BITS_PER_SAMPLE_TAG, SHORT,
1L,&bitsPerPixel) != noErr ||
TPutPtrTag(listH, COMPRESSION_TAG, SHORT,
1L, &compressType) != noErr ||
TPutPtrTag(listH, FILL_ORDER_TAG, SHORT,
1L, &fillOrder) != noErr ||
TPutPtrTag(listH, ORIENTATION_TAG, SHORT,
1L, &orientation) != noErr ||
TPutPtrTag(listH, PHOTOMETRIC_INTERP_TAG, SHORT,
1L, &photoInterp) != noErr ) {
ErrorMessage(BADPUTTAGS);
return;
}
/* leave room for header in output file */
SetEOF(refNum, (Int32)sizeof(TiffHeader));
nextFileFree = sizeof(TiffHeader);
/* Write out image to file, fixing from Macintosh rounding of rows to the
* nearest 2 bytes, to the TIFF rounding of rows to the nearest byte.
*/
if (oddRowBytes)
TUnfixOddRowBytes(myBitMapPtr); /* round rows to nearest byte */
startLine = 0;
bufferPtr = myBitMapPtr->baseAddr;
while (startLine < imageLength) {
numLines = MIN(imageLength - startLine, rowsPerStrip);
if (TWriteImageStrip(refNum, &nextFileFree, listH,
startLine, numLines, bufferPtr, plane) != noErr) {
ErrorMessage(BADWRITEIMAGE);
return;
}
startLine += numLines;
bufferPtr += numLines * tiffRowBytes;
}
if (oddRowBytes)
TFixOddRowBytes(myBitMapPtr); /* round rows to nearest word */
/* directory must be on word boundary */
if (nextFileFree % 2 != 0) {
/* add filler byte */
count = 1;
if (FSWrite(refNum, &count, &dummy) != noErr) {
ErrorMessage(BADWRITE);
return;
}
nextFileFree++;
}
dirOffset = nextFileFree;
if (TWriteTags(refNum, byteOrder, &nextFileFree, listH, 0L) != noErr) {
ErrorMessage(BADWRITETAGS);
return;
}
if (TWriteHeader(refNum, dirOffset, byteOrder) != noErr) {
ErrorMessage(BADWRITEHEADER);
}
}
/* Free the list handle and any memory allocated to image description structure.
*/
static void CleanUp(listHandle, idPtr)
Handle listHandle;
id *idPtr;
{
MyDisposPtr(&idPtr->bitsPerSample);
MyDisposPtr(&idPtr->compression);
MyDisposPtr(&idPtr->docName);
MyDisposPtr(&idPtr->imageDescription);
MyDisposPtr(&idPtr->make);
MyDisposPtr(&idPtr->model);
MyDisposPtr(&idPtr->stripOffsets);
MyDisposPtr(&idPtr->stripByteCounts);
MyDisposPtr(&idPtr->minSampleValue);
MyDisposPtr(&idPtr->maxSampleValue);
MyDisposPtr(&idPtr->pageName);
MyDisposPtr(&idPtr->freeOffsets);
MyDisposPtr(&idPtr->freeByteCounts);
MyDisposPtr(&idPtr->grayResponseCurve);
MyDisposPtr(&idPtr->colorResponseCurves);
DisposHandle(listHandle);
}
void InitID(idPtr)
id *idPtr;
{
idPtr->subfileType = -1;
idPtr->imageWidth = 0;
idPtr->imageLength = 0;
idPtr->bitsPerSample = nil;
idPtr->compression = nil;
idPtr->photoInterp = -1;
idPtr->threshholding = -1;
idPtr->cellWidth = -1;
idPtr->cellLength = -1;
idPtr->fillOrder = 0;
idPtr->docName = nil;
idPtr->imageDescription = nil;
idPtr->make = nil;
idPtr->model = nil;
idPtr->stripOffsets = nil;
idPtr->orientation = -1;
idPtr->samplesPerPixel = 0;
idPtr->rowsPerStrip = 0;
idPtr->stripsPerImage = 0;
idPtr->stripByteCounts = nil;
idPtr->minSampleValue = nil;
idPtr->maxSampleValue = nil;
idPtr->xResolution.numerator = 0;
idPtr->xResolution.denominator = 0;
idPtr->yResolution.numerator = 0;
idPtr->yResolution.denominator = 0;
idPtr->planarConfig = -1;
idPtr->pageName = nil;
idPtr->xPosition.numerator = 0;
idPtr->xPosition.denominator = 0;
idPtr->yPosition.numerator = 0;
idPtr->yPosition.denominator = 0;
idPtr->freeOffsets = nil;
idPtr->freeByteCounts = nil;
idPtr->grayResponseUnit = -1;
idPtr->grayResponseCurve = nil;
idPtr->group3Options = 0;
idPtr->group4Options = 0;
idPtr->resolutionUnit = -1;
idPtr->pageNumber[0] = 0;
idPtr->pageNumber[1] = 0;
idPtr->colorResponseUnit = -1;
idPtr->colorResponseCurves = nil;
}
TiffDirEntry GetDirEntry(listHandle, tagOffset)
Handle listHandle;
Int32 tagOffset;
{
Ptr p;
/* get pointer to tag list */
p = &(**listHandle); /* HANDLE DEREFERENCE */
/* get pointer to our tag's directory entry */
p += tagOffset;
/* return the whole Directory Entry Structure, not a pointer to it */
return(*((TiffDirEntry *)p));
}
Int16 TypeSize(type)
Int16 type;
{
switch (type) {
case BYTE:
return(BYTESIZE);
case ASCII:
return(ASCIISIZE);
case SHORT:
return(SHORTSIZE);
case LONG:
return(LONGSIZE);
case RATSIZE:
return(RATSIZE);
}
}
Ptr SetIDPtr(listHandle, tag, defaultValue, defaultType)
Handle listHandle;
Int16 tag;
Int32 defaultValue; /* won't handle defaults larger than 4 bytes */
Int32 defaultType;
{
TiffDirEntry tagDE;
Ptr p;
Int32 tagOffset,
size,
nvals;
if (TFindTag(listHandle, &tagOffset, tag)) {
tagDE = GetDirEntry(listHandle, tagOffset);
size = TypeSize(tagDE.type) * tagDE.length;
if ((p = MyNewPtr(size)) != nil)
TGetTag(listHandle, tagOffset, p, size);
}
else if (defaultType != 0) {
if ((p = MyNewPtr(defaultType)) != nil) {
switch (defaultType) {
case BYTE:
*(unsigned char *)p = defaultValue;
break;
case ASCII:
*(unsigned char *)p = defaultValue;
break;
case SHORT:
*(unsigned short *)p = defaultValue;
break;
case LONG:
*(unsigned long *)p = defaultValue;
break;
case RATIONAL:
((Rational *)p)->numerator = defaultValue;
((Rational *)p)->denominator = 1;
break;
}
}
}
else
p = nil;
return(p);
}