home *** CD-ROM | disk | FTP | other *** search
/ ftp.pasteur.org/FAQ/ / ftp-pasteur-org-FAQ.zip / FAQ / graphics / fileformats-faq / part4 < prev   
Encoding:
Internet Message Format  |  1997-01-20  |  20.8 KB

  1. Path: senator-bedfellow.mit.edu!bloom-beacon.mit.edu!eru.mt.luth.se!www.nntp.primenet.com!nntp.primenet.com!news1.mpcs.com!hammer.uoregon.edu!arclight.uoregon.edu!su-news-hub1.bbnplanet.com!cam-news-hub1.bbnplanet.com!news.bbnplanet.com!cpk-news-hub1.bbnplanet.com!cam-news-feed2.bbnplanet.com!amber.ora.com!not-for-mail
  2. From: jdm@ora.com
  3. Newsgroups: comp.graphics.misc,comp.answers,news.answers
  4. Subject: Graphics File Formats FAQ (Part 4 of 4): Tips and Tricks of the Trade
  5. Supersedes: <graphics/fileformats-faq-4-849730784@ora.com>
  6. Followup-To: poster
  7. Date: 20 Jan 1997 00:13:12 -0800
  8. Organization: O'Reilly & Associates, Inc.
  9. Lines: 550
  10. Sender: jdm@ruby.ora.com
  11. Approved: news-answers-request@MIT.EDU
  12. Distribution: world
  13. Expires: 02/24/97 00:13:00
  14. Message-ID: <graphics/fileformats-faq-4-853747980@ora.com>
  15. References: <graphics/fileformats-faq-1-853747980@ora.com>
  16. Reply-To: jdm@ora.com (James D. Murray)
  17. NNTP-Posting-Host: ruby.ora.com
  18. Summary: This document answers many of the most frequently asked 
  19.     questions about graphics file formats on Usenet.
  20. Keywords: FAQ, GRAPHICS, FORMAT, IMAGE, MULTIMEDIA, 3D
  21. Xref: senator-bedfellow.mit.edu comp.graphics.misc:18678 comp.answers:23814 news.answers:92563
  22.  
  23. Posted-By: auto-faq 3.1.1.2
  24. Archive-name: graphics/fileformats-faq/part4
  25. Posting-Frequency: monthly
  26. Last-modified: 20Jan97
  27.  
  28. Graphics File Formats FAQ (Part 4 of 4): Tips and Tricks of the Trade
  29.  
  30. ------------------------------
  31.  
  32. This FAQ (Frequently Asked Questions) list contains information on
  33. graphics file formats, including, raster, vector, metafile, Page
  34. Description Language, 3D object, animation, and multimedia formats.
  35.  
  36. This FAQ is divided into four parts, each covering a different area of
  37. graphics file format information:
  38.  
  39.   Graphics File Formats FAQ (Part 1 of 4): General Graphics Format Questions
  40.   Graphics File Formats FAQ (Part 2 of 4): Image Conversion and Display Programs
  41.   Graphics File Formats FAQ (Part 3 of 4): Where to Get File Format Specifications
  42.   Graphics File Formats FAQ (Part 4 of 4): Tips and Tricks of the Trade
  43.  
  44. Please email contributions, corrections, and suggestions about this FAQ to
  45. jdm@ora.com. Relevant information posted to newsgroups will not
  46. automatically make it into this FAQ.
  47.  
  48. -- James D. Murray
  49.  
  50. ------------------------------
  51.  
  52. Subject: 0. Contents of Tips and Tricks of the Trade 
  53. Subjects marked with <NEW> are new to this FAQ. Subjects marked with <UPD>
  54. have been updated since the last release  of this FAQ.
  55.  
  56. I. General questions about this FAQ
  57.  
  58. 0. Maintainer's Comments
  59. 1. What's new in this latest FAQ release?
  60.  
  61. II. Programming Tips for Graphics File Formats
  62.  
  63. 0. What's the best way to read a file header?
  64. 1. What's this business about endianness?
  65. 2. How can I determine the byte-order of a system at run-time?
  66. 3. How can I identify the format of a graphics file?
  67. 4. What are the format identifiers of some popular file formats?
  68.  
  69. III. Kudos and Assertions
  70.  
  71. 0. Acknowledgments
  72. 1. About The Author
  73. 2. Disclaimer
  74. 3. Copyright Notice
  75.  
  76.  
  77. ------------------------------
  78.  
  79.  
  80. Subject: I. General questions about this FAQ 
  81.  
  82. ------------------------------
  83.  
  84. Subject: 0. Maintainer's Comments
  85.  
  86. Programmer's are code-hungry people. They just want the secrets and they want
  87. them to work NOW! But always in the back of a hack's mind there are the
  88. questions: "Is this really the best way to do this? Could it be better?".
  89.  
  90. This FAQ is to share ideas on the implementation details of reading, writing,
  91. converting, and displaying graphics file formats. You'll probably get some
  92. good ideas here, find a few things you didn't know about, and even have a few
  93. suggestions and improvements of you own to add (send them to jdm@ora.com).
  94.  
  95. If you need to know the best way to do something with file formats, or
  96. just find it embarrassing to implement a chunk of some other programmer's
  97. code and then have to admit you really don't understand how it works, then
  98. this FAQ is for you.
  99.  
  100. ------------------------------
  101.  
  102. Subject: 1. What's new in this latest FAQ release?
  103.  
  104.   o Minor bug fixed in GetLittleWord() and GetLittleDword() functions
  105.  
  106. ------------------------------
  107.  
  108. Subject: II. Programming Tips for Graphics File Formats 
  109.  
  110. ------------------------------
  111.  
  112. Subject: 0. What's the best way to read a file header?
  113.  
  114. You wouldn't think there's a lot of mystery about reading a few bytes from
  115. a disk file, eh? Programmer's, however, are constantly loosing time
  116. because they don't consider a few problems that may occur and cause them
  117. to loose time. Consider the following code:
  118.  
  119.   typedef struct _Header
  120.   {
  121.     BYTE Id;
  122.     WORD Height;
  123.     WORD Width;
  124.     BYTE Colors;
  125.   } HEADER;
  126.  
  127.   HEADER Header;
  128.  
  129.   void ReadHeader(FILE *fp)
  130.   {
  131.     if (fp != (FILE *)NULL)
  132.       fread(&Header, sizeof(HEADER), 1, fp);
  133.   }
  134.  
  135. Looks good, right? The fread() will read the next sizeof(HEADER) bytes from
  136. a valid FILE pointer into the Header data structure. So what could go
  137. wrong?
  138.  
  139. The problem often encountered with this method is one of element alignment
  140. within structures. Compilers may pad structures with "invisible" elements
  141. to allow each "visible" element to align on a 2- or 4-byte address
  142. boundary.  This is done for efficiency in accessing the element while in
  143. memory. Padding may also be added to the end of the structure to bring
  144. it's total length to an even number of bytes. This is done so the data
  145. following the structure in memory will also align on a proper address
  146. boundary.
  147.  
  148. If the above code is compiled with no (or 1-byte) structure alignment the
  149. code will operate as expected. With 2-byte alignment an extra two bytes
  150. would be added to the HEADER structure in memory and make it appear as
  151. such:
  152.  
  153.   typedef struct _Header
  154.   {
  155.     BYTE Id;
  156.     BYTE Pad1;      // Added padding
  157.     WORD Height;
  158.     WORD Width;
  159.     BYTE Colors;
  160.     BYTE Pad2;      // Added padding
  161.   } HEADER;
  162.  
  163. As you can see the fread() will store the correct value in Id, but the
  164. first byte of Height will be stored in the padding byte. This will throw
  165. off the correct storage of data in the remaining part of the structure
  166. causing the values to be garbage.
  167.  
  168. A compiler using 4-byte alignment would change the HEADER in memory as such:
  169.  
  170.   typedef struct _Header
  171.   {
  172.     BYTE Id;
  173.     BYTE Pad1;      // Added padding
  174.     BYTE Pad2;      // Added padding
  175.     BYTE Pad3;      // Added padding
  176.     WORD Height;
  177.     WORD Width;
  178.     BYTE Colors;
  179.     BYTE Pad4;      // Added padding
  180.     BYTE Pad5;      // Added padding
  181.     BYTE Pad6;      // Added padding
  182.   } HEADER;
  183.  
  184. What started off as a 6-byte header increased to 8 and 12 bytes thanks to
  185. alignment. But what can you do? All the documentation and makefiles you
  186. write will not prevent someone from compiling with the wrong options flag
  187. and then pulling their (or your) hair out when your software appears not
  188. to work correctly.
  189.  
  190. Now considering this alternative to the ReadHeader() function:
  191.  
  192.   HEADER Header;
  193.  
  194.   void ReadHeader(FILE *fp)
  195.   {
  196.     if (fp != (FILE *)NULL)
  197.     {
  198.       fread(&Header.Id, sizeof(Header.Id), 1, fp);
  199.       fread(&Header.Height, sizeof(Header.Height), 1, fp);
  200.       fread(&Header.Width, sizeof(Header.Width), 1, fp);
  201.       fread(&Header.Colors, sizeof(Header.Colors), 1, fp);
  202.     }
  203.   }
  204.  
  205. What both you and your compiler now see is a lot more code. Rather than
  206. reading the entire structure in one, elegant shot, you read in each
  207. element separately using multiple calls to fread(). The trade-off here is
  208. increased code size for not caring what the structure alignment option of
  209. the compiler is set to. These cases are also true for writing structures
  210. to files using fwrite(). Write only the data and not the padding please.
  211.  
  212. But is there still anything we've yet over looked? Will fread() (fscanf(),
  213. fgetc(), and so forth) always return the data we expect?  Will fwrite()
  214. (fprintf(), fputc(), and so forth) ever write data that we don't want, or
  215. in a way we don't expect? Read on to the next section...
  216.  
  217. ------------------------------
  218.  
  219. Subject: 1. What's this business about endianness?
  220.  
  221. So you've been pulling you hair out trying to discover why your elegant
  222. and perfect-beyond-reproach code, running on your Macintosh or Sun, is
  223. reading garbage from PCX and TGA files. Or perhaps your MS-DOS or Windows
  224. application just can't seem to make heads or tails out of that Sun Raster
  225. file. And, to make matters even more mysterious, it seems your most
  226. illustrious creation will read some TIFF files, but not others.
  227.  
  228. As was hinted at in the previous section, just reading the header of a
  229. graphics file one field is not enough to insure data is always read correctly
  230. (not enough for portable code, anyway). In addition to structure, we must also
  231. consider the endianness of the file's data, and the endianness of the
  232. system's architecture our code is running on.
  233.  
  234. Here's are some baseline rules to follow:
  235.  
  236.   1) Graphics files typically use a fixed byte-ordering scheme. For example, 
  237.      PCX and TGA files are always little-endian; Sun Raster and Macintosh
  238.      PICT are always big-endian.
  239.   2) Graphics files that may contain data using either byte-ordering scheme
  240.      (for example TIFF) will have an identifier that indicates the
  241.      endianness of the data.
  242.   3) ASCII-based graphics files (such as DXF and most 3D object files),
  243.      have no endianness and are always read in the same way on any system.
  244.   4) Most CPUs use a fixed byte-ordering scheme. For example, the 80486
  245.      is little-endian and the 68040 is big-endian.
  246.   5) You can test for the type of endianness a system using software.
  247.   6) There are many systems that are neither big- nor little-endian; these
  248.      middle-endian systems will possibly cause such byte-order detection
  249.      tests to return erroneous results.
  250.  
  251. Now we know that using fread() on a big-endian system to read data from a
  252. file that was originally written in little-endian order will return
  253. incorrect data. Actually, the data is correct, but the bytes that make up
  254. the data are arranged in the wrong order. If we attempt to read the 16-bit
  255. value 1234h from a little-endian file, it would be stored in memory using
  256. the big-endian byte-ordering scheme and the value 3412h would result. What
  257. we need is a swap function to change the resulting position of the bytes:
  258.  
  259.   WORD SwapTwoBytes(WORD w)
  260.   {
  261.       register WORD tmp;
  262.       tmp =  (w & 0x00FF);
  263.       tmp = ((w & 0xFF00) >> 0x08) | (tmp << 0x08);
  264.       return(tmp);
  265.   }
  266.   
  267. Now we can read a two-byte header value and swap the bytes as such:
  268.  
  269.   fread(&Header.Height, sizeof(Header.Height), 1, fp);
  270.   Header.Height = SwapTwoBytes(Header.Height);
  271.  
  272. But what about four-byte values? The value 12345678h would be stored as
  273. 78563412h. What we need is a swap function to handle four-byte values:
  274.  
  275.   DWORD SwapFourBytes(DWORD dw)
  276.   {
  277.       register DWORD tmp;
  278.       tmp =  (dw & 0x000000FF);
  279.       tmp = ((dw & 0x0000FF00) >> 0x08) | (tmp << 0x08);
  280.       tmp = ((dw & 0x00FF0000) >> 0x10) | (tmp << 0x08);
  281.       tmp = ((dw & 0xFF000000) >> 0x18) | (tmp << 0x08);
  282.       return(tmp);
  283.   }
  284.  
  285. But how do we know when to swap and when not to swap? We always know the
  286. byte-order of a graphics file that we are reading, but how do we check
  287. what the endianness of system we are running on is? Using the C language,
  288. we might use preprocessor switches to cause a conditional compile based on
  289. a system definition flag:
  290.  
  291.   #define MSDOS     1
  292.   #define WINDOWS   2
  293.   #define MACINTOSH 3
  294.   #define AMIGA     4
  295.   #define SUNUNIX   5
  296.   
  297.   #define SYSTEM    MSDOS
  298.   
  299.   #if defined(SYSTEM == MSDOS)  
  300.     // Little-endian code here
  301.   #elif defined(SYSTEM == WINDOWS)  
  302.     // Little-endian code here
  303.   #elif defined(SYSTEM == MACINTOSH)  
  304.     // Big-endian code here
  305.   #elif defined(SYSTEM == AMIGA)  
  306.     // Big-endian code here
  307.   #elif defined(SYSTEM == SUNUNIX)  
  308.     // Big-endian code here
  309.   #else
  310.   #error Unknown SYSTEM definition
  311.   #endif
  312.  
  313. My reaction to the above code was *YUCK!* (and I hope yours was too!).  A
  314. snarl of fread(), fwrite(), SwapTwoBytes(), and SwapFourBytes() functions
  315. laced between preprocessor statements is hardly elegant code, although
  316. sometimes it is our best choice. Fortunately, this is not one of those
  317. times.
  318.  
  319. What we first need is a set of functions to read the data from a file
  320. using the byte-ordering scheme of the data. This effectively combines the
  321. read\write and swap operations into one set of functions. Considering the
  322. following:
  323.  
  324.   WORD GetBigWord(FILE *fp)
  325.   {
  326.       register WORD w;
  327.       w =  (WORD) (fgetc(fp) & 0xFF);
  328.       w = ((WORD) (fgetc(fp) & 0xFF)) | (w << 0x08);
  329.       return(w);
  330.   }
  331.   
  332.   WORD GetLittleWord(FILE *fp)
  333.   {
  334.       register WORD w;
  335.       w =  (WORD) (fgetc(fp) & 0xFF);
  336.       w |= ((WORD) (fgetc(fp) & 0xFF) << 0x08);
  337.       return(w);
  338.   }
  339.   
  340.   DWORD GetBigDoubleWord(FILE *fp)
  341.   {
  342.       register DWORD dw;
  343.       dw =  (DWORD) (fgetc(fp) & 0xFF);
  344.       dw = ((DWORD) (fgetc(fp) & 0xFF)) | (dw << 0x08);
  345.       dw = ((DWORD) (fgetc(fp) & 0xFF)) | (dw << 0x08);
  346.       dw = ((DWORD) (fgetc(fp) & 0xFF)) | (dw << 0x08);
  347.       return(dw);
  348.   }
  349.   
  350.   DWORD GetLittleDoubleWord(FILE *fp)
  351.   {
  352.       register DWORD dw;
  353.       dw =  (DWORD) (fgetc(fp) & 0xFF);
  354.       dw |= ((DWORD) (fgetc(fp) & 0xFF) << 0x08);
  355.       dw |= ((DWORD) (fgetc(fp) & 0xFF) << 0x10);
  356.       dw |= ((DWORD) (fgetc(fp) & 0xFF) << 0x18);
  357.       return(dw);
  358.   }
  359.   
  360.   void PutBigWord(WORD w, FILE *fp)
  361.   {
  362.       fputc((w >> 0x08) & 0xFF, fp);
  363.       fputc(w & 0xFF, fp);
  364.   }
  365.   
  366.   void PutLittleWord(WORD w, FILE *fp)
  367.   {
  368.       fputc(w & 0xFF, fp);
  369.       fputc((w >> 0x08) & 0xFF, fp);
  370.   }
  371.   
  372.   void PutBigDoubleWord(DWORD dw, FILE *fp)
  373.   {
  374.       fputc((dw >> 0x18) & 0xFF, fp);
  375.       fputc((dw >> 0x10) & 0xFF, fp);
  376.       fputc((dw >> 0x08) & 0xFF, fp);
  377.       fputc(dw & 0xFF, fp);
  378.   }
  379.   
  380.   void PutLittleDoubleWord(DWORD dw, FILE *fp)
  381.   {
  382.       fputc(dw & 0xFF, fp);
  383.       fputc((dw >> 0x08) & 0xFF, fp);
  384.       fputc((dw >> 0x10) & 0xFF, fp);
  385.       fputc((dw >> 0x18) & 0xFF, fp);
  386.   }
  387.  
  388. If we were reading a little-endian file on a big-endian system (or visa
  389. versa), the previous code:
  390.  
  391.   fread(&Header.Height, sizeof(Header.Height), 1, fp);
  392.   Header.Height = SwapTwoBytes(Header.Height);
  393.  
  394. Would be replaced by:
  395.   
  396.   Header.Height = GetLittleWord(fp);
  397.  
  398. The code to write the same value to a file would be changed from:
  399.  
  400.   Header.Height = SwapTwoBytes(Header.Height);
  401.   fwrite(&Header.Height, sizeof(Header.Height), 1, fp);
  402.  
  403. To the slightly more readable:
  404.  
  405.   PutLittleWord(Header.Height, fp);
  406.  
  407. Note that these functions are the same regardless of the endianness of a
  408. system. For example, the ReadLittleWord() will always read a two-byte value
  409. from a little-endian file regardless of the endianness of the system;
  410. PutBigDoubleWord() will always write a four-byte big-endian value, and so
  411. forth.
  412.  
  413. ------------------------------
  414.  
  415. Subject: 2. How can I determine the byte-order of a system at run-time?
  416.  
  417. You may wish to optimize how you read (or write) data from a graphics file
  418. based on the endianness of your system. Using the GetBigDoubleWord()
  419. function mentioned in the previous section to read big-endian data from a
  420. file on a big-endian system imposes extra overhead we don't really need
  421. (although if the actual number of read/write operations in your program is
  422. small you might not consider this overhead to be too bad).
  423.  
  424. If our code could tell what the endianness of the system was at run-time,
  425. it could choose (using function pointers) what set of read/write functions
  426. to use. Look at the following function:
  427.  
  428.   #define BIG_ENDIAN      0
  429.   #define LITTLE_ENDIAN   1
  430.  
  431.   int TestByteOrder(void)
  432.   {
  433.       short int word = 0x0001;
  434.       char *byte = (char *) &word;
  435.       return(byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN);
  436.   }
  437.  
  438. This code assigns the value 0001h to a 16-bit integer. A char pointer is
  439. then assigned to point at the first (least-significant) byte of the
  440. integer value.  If the first byte of the integer is 01h, then the system
  441. is little-endian (the 01h is in the lowest, or least-significant,
  442. address). If it is 00h then the system is big-endian.
  443.  
  444. ------------------------------
  445.  
  446. Subject: 3. How can I identify the format of a graphics file?
  447.  
  448. When writing any type of file or data stream reader it is very important
  449. to implement some sort of method for verifying that the input data is in
  450. the format you expect. Here are a few methods:
  451.  
  452. 1) Trust the user of your program to always supply the correct data,
  453. thereby freeing you from the tedious task of writing any type of format
  454. identification routines. Choose this method and you will provide solid
  455. proof that contradicts the popular claim that users are inherently far
  456. more stupid than programmers.
  457.  
  458. 2) Read the file extension or descriptor. A GIF file will always have the
  459. extension .GIF, right? Targa files .TGA, yes?  And TIFF files will have an
  460. extension of .TIF or a descriptor of TIFF. So no problem?
  461.  
  462. Well, for the most part, this is true. This method certainly isn't
  463. bulletproof, however.  Your reader will occasionally be fed the odd-batch
  464. of mis-label files ("I thought they were PCX files!"). Or files with
  465. unrecognized mangled extensions  (.TAR rather than .TGA or .JFI rather
  466. than .JPG) that your reader knows how to read, but won't read because it
  467. doesn't recognize the extensions. File extensions also won't usually tell
  468. you the revision of the file format you are reading (with some revisions
  469. creating an almost entirely new format). And more than one file format
  470. share the more common file extensions (such as .IMG and .PIC). And last of
  471. all, data streams have no file extensions or descriptors to read at all.
  472.  
  473. 3) Read the file and attempt to recognize the format by specific patterns
  474. in the data. Most file formats contain some sort of identifying pattern of
  475. data that is identical in all files. In some cases this pattern gives and
  476. indication of the revision of the format (such as GIF87a and GIF89a) or
  477. the endianness of the data format.
  478.  
  479. Nothing is easy, however. Not all formats contain such identifiers (such
  480. as PCX). And those that do don't necessarily put it at the beginning of
  481. the file. This means if the data is in the format of a stream you many
  482. have to read (and buffer) most or all of the data before you can determine
  483. the format. Of course, not all graphics formats are suitable to be read as
  484. a data stream anyway.
  485.  
  486. Your best bet for a method of format detection is a combination of methods
  487. two and three. First believe the file extension or descriptor, read some
  488. data, and check for identifying data patterns. If this test fails, then
  489. attempt to recognize all other known patterns.
  490.  
  491. Run-time file format identification a black-art at best.
  492.  
  493. ------------------------------
  494.  
  495. Subject: 4. What are the format identifiers of some popular file formats?
  496.  
  497. Here are a few algorithms that you can use to determine the format of a
  498. graphics file at run-time.
  499.  
  500.  
  501. GIF: The first six bytes of a GIF file will be the byte pattern of 
  502.      474946383761h ("GIF87a") or 474946383961h ("GIF89a").
  503.  
  504. JFIF: The first three bytes are ffd8ffh (i.e., an SOI marker followed
  505.       by any marker). Do not check the fourth byte, as it will vary.
  506.  
  507. JPEG: The first three bytes are ffd8ffh (i.e., an SOI marker followed
  508.       by any marker). Do not check the fourth byte, as it will vary.
  509.       This works with most variants of "raw JPEG" as well.
  510.  
  511. PNG: The first eight bytes of all PNG files are 89504e470d0a1a0ah.
  512.  
  513. SPIFF: The first three bytes are ffd8ffh (i.e., an SOI marker followed
  514.        by any marker). Do not check the fourth byte, as it will vary.
  515.  
  516. Sun: The first four bytes of a Sun Rasterfile are 59a66a95h. If you have
  517.      accidentally read this identifier using the little-endian byte order
  518.      this value will will be read as 956aa659h.
  519.  
  520. TGA: The last 18 bytes of a TGA Version 2 file is the string
  521.      "TRUEVISION-XFILE.\0". If this string is not present, then the file
  522.      is assumed to be a TGA Version 1 file.
  523.  
  524. TIFF: The first four bytes of a big-endian TIFF files are 4d4d002ah and
  525.       49492a00h for little-endian TIFF files.
  526.  
  527. ------------------------------
  528.  
  529. Subject: III. Kudos and Assertions 
  530.  
  531. ------------------------------
  532.  
  533. Subject: 0. Acknowledgments
  534.  
  535.   Chris M. Cooney <cooney1@imssys.imssys.com>
  536.   Tom Lane <tgl@netcom.com>
  537.   Charles R. Patton <crpatton@ingr.com>
  538.  
  539. ------------------------------
  540.  
  541. Subject: 1. About The Author
  542.  
  543. The author of this FAQ, James D. Murray, lives in the City of Orange,
  544. Orange County, California, USA. He is the co-author of the book
  545. Encyclopedia of Graphics File Formats published by O'Reilly and
  546. Associates, makes a living writing books for O'Reilly, writing
  547. telecommuncations network management software in C++ and Visual Basic,
  548. and may be reached as jdm@ora.com,
  549. or via U.S. Snail at: P.O. Box 70, Orange, CA 92666-0070 USA.
  550.  
  551. ------------------------------
  552.  
  553. Subject: 2. Disclaimer
  554.  
  555. While every effort has been taken to insure the accuracy of the
  556. information contained in this FAQ list compilation, the author and
  557. contributors assume no responsibility for errors or omissions, or for
  558. damages resulting from the use of the information contained herein.
  559.  
  560. ------------------------------
  561.  
  562. Subject: 3. Copyright Notice
  563.  
  564. This FAQ is Copyright 1994-96 by James D. Murray. This work may be
  565. reproduced, in whole or in part, using any medium, including, but not
  566. limited to, electronic transmission, CD-ROM, or published in print, under
  567. the condition that this copyright notice remains intact.
  568.  
  569. ------------------------------
  570.  
  571.