Adding Code for Writing a New File Format

Note: Despite the wide variety of displays and file formats xv deals with, internally it only manipulates either 8-bit colormapped images or 24-bit RGB images. Your Write() routine must be prepared to take either sort of image, and convert it (if necessary) to the image format that your file format dictates.

If you haven't already done so (if/when you created the Load() function):

Make a copy of xvpbm.c , calling it something appropriate. For the rest of this appendix, mentally replace the string ' xvpbm.c ' with the name of your new file.

Edit the Makefile and/or the Imakefile so that your new module will be compiled. In the Makefile , add " xvpbm.o " to the " OBJS = ... " macro definition. In the Imakefile , add " xvpbm.o " to the end of the " OBJS1 = ... " macro definition, and " xvpbm.c" to the end of the "SRCS1 = ..." macro definition.

Edit the new module.

You'll need to #include "xv.h" , of course.

The module should have one externally callable function that does the work of writing the file. The function is called with a large number of arguments, described below. The function should return '0' if everything succeeded, and '-1' on failure.

int WritePBM(fp,pic,ptype,w,h,rmap,gmap,bmap,numcols,colorstyle,raw,comment)
     FILE *fp;
     byte *pic;
     int   ptype, w,h;
     byte *rmap, *gmap, *bmap;
     int   numcols, colorstyle, raw;
     char *comment;
file *fp;
This is a pointer to an already- fopen() 'd stream. Your function should neither open nor close this stream, as that all gets handled elsewhere in xvdir.c .
byte *pic;
This points to the image data that will be written. In the case of a PIC8 image, pic will point to a w*h long array of bytes, one byte per pixel, starting at the top-left, and proceeding in normal scan-line order. There is no padding of any sort at the end of the lines.
In the case of a PIC24 image, pic will point to a w*h*3 long array of bytes. There are three bytes per pixel, in red, green, blue order. The pixels start at the top-left, and proceed in normal scan line order. There is no padding of any sort at the end of the lines.
int ptype, w, h;
These variables describe the format of pic . ptype can be set to either PIC8 or PIC24 . w and h are the width and height of the image, in pixels.
byte *rmap, *gmap, *bmap;
int numcols;
These pointers point to the colormap associated with the image. They are only relevant when ptype is PIC8, meaning that pic is an 8-bit per pixel colormapped image. These arrays will each be numcols entries long, with a maximum length of 256. Do not attempt to access entries >= numcols , as the colormaps are not necessarily 256 entries long. You are guaranteed that pixel values found in pic will be within the range [0..numcols-1], so you don't have to check each pixel value. Also, do not attempt to access these arrays at all if ptype is PIC24, as these pointers will probably be NULL in that case.
int colorstyle;
The Colors choice selected in the xv save window. It can be either F_FULLCOLOR , F_GREYSCALE , or F_BWDITHER . It will not be F_REDUCED . If the user selects Reduced Color in the xv save window, the appropriate image will be computed, and you'll be given that image, and colorstyle will be set to F_FULLCOLOR .
Likewise, if the user has selected B/W Dithered in the xv save window, an appropriate black-and-white image will have been generated before your Write() routine is called, so you won't have to worry about that. Such an image will be a PIC8 image, with a 2-entry colormap. It is up to you to decide which of the two colors should be written as black, and which should be written as white. You should do this by comparing the values of MONO(rmap[0],gmap[0],bmap[0]) and MONO(rmap[1],gmap[1],bmap[1]). Whichever value is smaller is the darker of the two, and should be written as black.
int raw;
This is a value passed in specifically for the WritePBM() function, as PBM has two closely-related subformats (raw, and ascii) both of which are written by this one function. Your function won't need this, nor should it be passed in to your function.
char *comment;
This will point to a zero-terminated character string which contains the comments that should be written into the image file. Note that this string can be of any length, and it may contain any number of lines (separated by '\n' characters). If your image format supports comments in the file, you should write this information to the file. If it doesn't, you should just ignore this variable. Also, this variable may be NULL, (signifying 'no comments'), in which case it should not be used.

You may pass more parameters, since you're going to be adding the call to this function later on. For example, in my PBM code, I pass one more parameter, 'raw' (whether to save the file as 'raw' or 'ascii') to handle two very similar formats. (Rather than having to write WritePBMRaw() and WritePBMAscii() functions.)

Write the function as you deem appropriate. See xvpbm.c for an example of a Write() routine that writes different formats for 1-bit per pixel images, 8-bit per pixel images, and 24-bit per pixel images, based on ptype and colorstyle .

Note: If your file format can only handle 8-bit images, and ptype is set to PIC24 , you will have to call Conv24to8() to convert the 24-bit image into an 8-bit colormapped image that you can write to the file. See xvgifwr.c for an example of how this is done.

That done, edit ' xv.h ' and add a function declaration for your new function. Search for ' WritePBM() ' in the file for a sample declaration to copy.

Also find the block that begins with:

#define F_GIF       0
#define F_JPEG      ( 0 + F_JPGINC)

and add a definition for your format. Note that it'll be easiest to tack it on at the end.

These numbers must be contiguous, as they are used as indices into the fmtMB menu in xvdir.c .

Edit 'xvdir.c'. This is the module that controls the xv save window.

Add another format name, in the appropriate position, to the saveFormats[] string array.

In the function DoSave() , find the following block:

switch (fmt) {
  case F_GIF:
    rv = WriteGIF(fp, thepic, ptype, w, h, rp, gp, bp, nc, col,
  case F_PM:
    rv = WritePM (fp, thepic, ptype, w, h, rp, gp, bp, nc, col,

and add a case for your function.

Finally, if your format has a common 3 or 4 letter filename suffix (like, ".gif", ".jpg", etc.), you should modify the changeSuffix() routine in xvdir.c so that it recognizes your suffix, and puts your suffix on when someone selects your format.

And It's just that easy!

Writing Complex Formats

Okay, maybe it's not that easy...

If your format requires some additional information to specify how the file should be saved (such as the 'quality' setting in JPEG, or position/size parameters in PostScript), then your task is somewhat more difficult. You'll have to create some sort of pop-up dialog box to get the additional information that you want. You'll also have to change the way your Write() function gets called (as it will now get called from your pop-up dialog box). (Though, if you only feel like doing a quick hack, you can probably just use the GetStrPopUp() function to get a one-line character string from the user, and avoid the complication of writing your own dialog box.)

This is not recommended for anyone who doesn't understand Xlib programming. Frankly, it's not recommended for those who do, either, but they at least stand some chance of success.

The more adventurous types who wish to pursue this should take a look at the xvjpeg.c code, which implements an extremely simple pop-up dialog. A considerably more complicated dialog box is implemented in xvps.c. In addition to writing a module like these for your format, you'll also have to add the appropriate hooks to the DoSave() function (in xvdir.c) and the HandleEvent() function (in xvevent.c ). 'grep PS *.c' will be helpful in finding places where the xvps.c module is called.