XV: Adding Other Image Formats to xv, part 2/2

## 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,
>     picComments);
>       break;
>       case F_PM:
>         rv = WritePM (fp, thepic, ptype, w, h, rp, gp, bp, nc, col,
>     picComments);
>       break;

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.