As you might guess, writing a graphics file is basically the inverse of reading it. Writers may send data directly to an output device, such as a printer, or they may create image files and store data in them.
When you write a graphics file header, you must be careful to initialize all of the fields in the header with the correct data. Initialize reserved fields used for fill and padding with the value 00h, unless the file format specification states otherwise. You must write the header data to the graphics files using the correct byte order for the file format as well.
Because the GetWord() and GetDword() functions were so handy for correctly reading a header, their siblings PutWord() and PutDword() must be just as handy for writing one:
/* ** Put a 16-bit word in either big- or little-endian byte order. */ void PutWord(char byteorder, FILE *fp, WORD w) { if (byteorder == MSB_FIRST) { fputc((w >> 0x08) & 0xFF, fp); fputc( w & 0xFF, fp); } else /* LSB_FIRST */ { fputc( w & 0xFF, fp); fputc((w >> 0x08) & 0xFF, fp); } } /* ** Put a 32-bit word in either big- or little-endian byte order. */ void PutDword(char byteorder, FILE *fp, DWORD w) { if (byteorder == MSB_FIRST) { fputc((w >> 0x18) & 0xFF, fp); fputc((w >> 0x10) & 0xFF, fp); fputc((w >> 0x08) & 0xFF, fp); fputc( w & 0xFF, fp); } else /* LSB_FIRST */ { fputc( w & 0xFF, fp); fputc((w >> 0x08) & 0xFF, fp); fputc((w >> 0x10) & 0xFF, fp); fputc((w >> 0x18) & 0xFF, fp); } }
In the following example, we use fwrite(), PutWord(), and PutDword() to write out our header structure. Note that the byteorder argument in PutWord() and PutDword() indicates the byte order of the file we are writing (in this case, little-endian), and not the byte order of the machine on which the functions are being executed. Also, we indicate in fopen() that the output file is being opened for writing in binary mode (wb):
typedef struct _Header { DWORD FileId; BYTE Type; WORD Height; WORD Width; WORD Depth; CHAR FileName[81]; DWORD Flags; BYTE Filler[32]; } HEADER; int WriteHeader() { HEADER header; FILE *fp = fopen("MYFORMAT.FOR", "wb"); if (fp) { header.FileId = 0x91827364; header.Type = 3; header.Depth = 8; header.Height = 512; header.Width = 512; strncpy((char *)header.FileName, "MYFORMAT.FOR", sizeof(header.FileName)); header.Flags = 0x04008001; memset(&header.Filler, 0, sizeof(header.Filler)); PutDword(MSB_FIRST, fp, header.FileId); fputc(header.Type, fp); PutWord(MSB_FIRST, fp, header.Height); PutWord(MSB_FIRST, fp, header.Width); PutWord(MSB_FIRST, fp, header.Depth); fwrite(&header.FileName, sizeof(header.FileName), 1, fp); PutDword(MSB_FIRST, fp, header.Flags); fwrite(&header.Filler, sizeof(header.Filler), 1, fp); fclose(fp); return(0); } return(1); }
Writing binary data can be a little more complex than just making sure you are writing data in the correct byte order. Many formats specify that each scan line is to be padded out to end on a byte or word boundary if it does not naturally do so. When the scan-line data is read from the file, this padding (usually a series of zero bit values) is thrown away, but it must be added again later if the data is written to a file.
Image data that is written uncompressed to a file may require a conversion before it is written. If the data is being written directly from video memory, it may be necessary to convert the orientation of the data from pixels to planes, or from planes to pixels, before writing the data.
If the data is to be stored in a compressed format, the quickest approach is to compress the image data in memory and use fwrite() to write the image data to the graphics file. If you don't have a lot of memory to play with, then write out the compressed image data as it is encoded, usually one scan line at a time.
Copyright © 1996, 1994 O'Reilly & Associates, Inc. All Rights Reserved.