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

# Adding Other Image Formats to xv

This appendix is split up into two sections, one for reading a new file format, and the other for writing a new file format. Note that you do not necessarily have to read and write a new file format. For example, xv can read PCX files, but it doesn't write them. Likewise, xv traditionally could only write PostScript files, but couldn't read them. (And technically, it still doesn't.)

For the purposes of this example, I'll be talking about the PBM/PGM/PPM code specifically. (See the file `xvpbm.c` for full details.)

## Writing Code for Reading a New File Format

Note: Despite the wide variety of displays and file formats xv can deal with, internally it only manipulates 8-bit colormapped images or 24-bit RGB images. If you're loading an 8-bit colormapped image, such as a GIF image, no problem. If you're loading an 8-or-fewer-bits format that doesn't have a colormap (such as an 8-bit greyscale image, or a 1-bit B/W bitmap) your `Load` () routine will have to generate an appropriate colormap.

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 loading up the file. The function is called with two arguments, a filename to load, and a pointer to a `PICINFO` structure, like so:

    /*******************************************/
    int LoadPBM(fname, pinfo)
    char *fname;   PICINFO *pinfo;
    /*******************************************/

The file name will be the complete file name (absolute, not relative to any directory). Note: if xv is reading from `stdin`, don't worry about it. `stdin` is always automatically copied to a temporary file. The same goes for pipes and compressed files. Your `Load()` routine is guaranteed that it will be reading from a real file that appears to be in your file format, not a stream. This lets you use routines such as `fseek()` , and such.

The `pinfo` argument is a pointer to a `PICINFO` structure. This structure is used to hold the complete set of information associated with the image that will be loaded. When your `Load()` routine is called, the fields in this structure will all be zeroed-out. It is your function's responsibility to load up the structure appropriately, and completely. The structure is defined as:

>     /* info structure filled in by the LoadXXX() image reading routines */
>      typedef struct {byte *pic;                  /* image data */
>                      int   w, h;                 /* size */
>                      int   type;                 /* PIC8 or PIC24 */
>                      byte  r[256],g[256],b[256]; /* colormap, if PIC8 */
>                      int   normw, normh;         /* normal size of image.
>                                                     normally == w,h except when
>                                                     doing quick load for icons*/
>                      int   frmType;              /* def. Format type to save in */
>                      int   colType;              /* def. Color type to save in */
>                      char  fullInfo[128];        /* Format: field in info box */
>                      char  shrtInfo[128];        /* short format info */
>                      char *comment;              /* comment text */
>                      int   numpages;             /* # of page files, if >1 */
>                      char  pagebname[64];        /* basename of page files */
>      } PICINFO;

`The Load()` function should return '1' on success, '0' on failure.

All other information is communicated using the `PICINFO` structure. The fields should be setup as follows:

`byte *pic;`
  This is an array of bytes which holds the returned image. The array is `malloc()` 'd by the `Load()` routine. The array should be `w*h` bytes long (for an 8-bit colormapped image) or `w*h*3` bytes long (for a 24-bit RGB image). For an 8-bit image, there is one byte per pixel, which serves as an index into the returned colormap (see below). For a 24-bit image, there are three bytes per pixel, in red, green blue, order. In either case, pixels start at the top-left corner, and proceed in normal scan-line order. There is no padding of any sort at the end of a scan line.
`int w, h;`
  These variables specify the width and height (in pixels) of the image that has been loaded.
`int type;`
  This variable is used to tell the calling routine whether the loaded image is an 8-bit image or a 24-bit image. It must be set equal to `PIC8` or `PIC24` , whichever one is appropriate. These constants are defined in ' `xv.h` '.
`byte r[256], g[256], b[256];`
  If the returned image is an 8-bit image, you must load up these variables with the colormap for the image. A given pixel value in `pic` maps to an RGB color through these arrays. In each array, a value of 0 means 'off', and a value of 255 means 'fully on'. Note: the arrays do not have to be completely filled. Only RGB entries for pixels that actually exist in `pic` need to be set. For example, if `pic` is known to be a B/W bitmap with pixel values of 0 and 1, you would only have to set entries '0' and '1' of the `r,g,b` arrays.
  On the other hand, if the returned image is a 24-bit image, the `r,g,b` arrays are ignored, and you do not have to do anything with them.
`int normw, normh;`
  These specify the 'normal' size of the image. Normally, they are equal to `w` and `h`, respectively. The only exception is when doing a 'quick' load for icon generation, in which case it may be possible to read a 'reduced' version of the image, sufficient for generating the tiny icon files. In such a case, `w` and `h` would reflect the (reduced) size of the image returned, and `normw` and `normh` would reflect the 'normal' image size, for use in the comments displayed in the xv visual schnauzer. Currently only the `LoadJFIF()` function in `xvjpeg.c` actually does this.
`int frmType;`
  This lets you specify the Format type to automatically select when you Save a file. As such, this is only relevant if you intend to have xv write your image format as well as read it. If you are only writing an image loader, you should set this field to ' `-1` '. On the other hand, if you do intend to write a `Write()` function for your format, you should edit `xv.h` , find the `F_*` format definitions, and add one for your format. See `xvpcx.c` for an example of a load-only format, or `xvpbm.c` for a load-and-save format.
`int colType;`
  Used to determine which Colors setting should be used by default when you save a file. Since xv will use this setting no matter what format you're using, you must fill this field in appropriately regardless of whether or not you plan to have a `Write()` function for your format. This field should be set to `F_FULLCOLOR` for any type of color image, `F_GREYSCALE` for greyscale images, and `F_BWDITHER` for black-and-white 1-bit images. If in doubt, `F_FULLCOLOR` is always a safe choice, though it'd be nice if your module 'does the right thing'. (For instance if you read colormapped images, you should check to see if the colormap consists only of shades of grey, and set `F_GREYSCALE` if it does.)
`char fullInfo[128];`
  This string will be shown in the Format field of the xv info window. It should be set to something like this:

  `Color PPM, raw format (12345 bytes)`

`char shrtInfo[128];`
  A 'short' version of the info string. This gets displayed in the info line at the bottom of the xv controls and xv info windows when the image is loaded. It should look like this:
  `512x400 PPM.`
`char *comment;`
  If your image file format supports some sort of comment field, and you find one in the file, you should `malloc()` a pointer to a null-terminated string and copy any and all comments into this field. If there are multiple comments in a file, you should concatenate them together to form one long string. This string MUST be null-terminated, as xv will expect to be able to use `strlen()` on it, and possibly other 'string' functions.
`int numpages; char pagebname[64];`
  These two fields will only be used if your are writing a `Load()` function for a format that may have multiple images per file. If your format only ever has a single image per file, you don't have to worry about (or do anything with) these two fields.
  On the other hand, if your format does do multiple images per file, and the current file has more than one image in it, then what your program should do is split the multi-image file up into a temporary collection of single-image files, which should probably live in `/tmp` or something. Once you've done so, you should return the number of files created in `numpages` , and the 'base' filename of the page files in `pagebname` . The files created should have a common 'base', with the page number appended. (e.g., "/tmp/xvpg12345a.1", "/tmp/xvpg12345a.2", etc., where "/tmp/xvpg12345a." is the base filename (created by the `mktemp()` system function)) You should also load the first file and return its image in the normal way.
  See the `LoadPS()` function in `xvps.c` for a complete example of how this is done. Also, note that if your format supports multiple image per file, you should also pass in a ' `quick` ' parameter, which will tell your function to only load the first 'page' of the file. This is used by the visual schnauzer, which needs to load images when it generates icon files. To speed things up, the schnauzer tells the `Load()` function to only load the first page, as that's all it need to generate the icon file.

### Error Handling

Non-fatal errors in your `Load()` routine should be handled by calling the function `SetISTR(ISTR_WARNING, "%s: %s", bname, err)`, and returning a zero value. Where bname is the 'simple' name of your file (which can be obtained using `BaseName()` function in `xvmisc.c` ), and err should be an appropriate error string.

Non-fatal errors are considered to be errors that only affect the success of loading this one image, and not the continued success of running xv. For instance, "can't open file", "premature EOF", "garbage in file", etc. are all non-fatal errors. On the other hand, not being able to allocate memory (unsuccessful returns from `malloc()` ) is considered a fatal error, as it means xv is likely to run out of memory in the near future anyhow.

Fatal errors should be handled by calling `FatalError(error_string)` . This function prints the string to `stderr` , and exits the program with an error code.

Warnings (such as 'truncated file') that may need to be noted can be handled by calling `SetISTR()` as shown above, but continuing to return '1' from the `Load()` routine, signifying success.

Also, if your load routine fails for any reason, it is your responsibility to `free()` any pointers allocated (such as the pic field and the comment field, and return NULL in these fields). Otherwise, there'll be memory leaks whenever an image load fails.

### Hooking it up to xv

Once you have written a `Load()` routine, you'll want to hook it up to the xv source.

Edit `xv.h` and add a function prototype for any global functions you've written (presumably just `LoadPBM()` in this case). Follow the style used for the other `Load*()` function declarations.

Find the `RFT_*` definitions and tack one on the end for your format (e.g., `RFT_PBM` ). This is a list of values that ' `ReadFileType()` ' can return. We'll be working on that soon enough.

Edit `xv.c` :

1. Tell the `ReadFileType()` routine about your format. Add an 'else-if' case that determines if the file in question is in your format. Note that it must be possible to uniquely identify your format by reading the first 16 characters (or so) of the file. If your file format doesn't have some sort of magic number, you won't be able to conveniently hook it into xv, though you can certainly come up with some sort of kludge...
2. Tell the `ReadPicFile()` routine about your format. Add another `case` for your format type, and have it call your `Load()` routine with the appropriate arguments.
3. Hook your file up into the visual schnauzer. Edit the file `xvbrowse.c`

The first thing you have to do is create a 'generic' icon for your file format. Copy one of the existing ones (such as ' `bits/br_pbm.xbm` ') to get the size and the general 'look' correct.

`#include` this icon at the top of the file.

Add an appropriately-named `BF_*` definition to the end of the list, and increase `BF_MAX` appropriately.

Have the icon pixmap created in the `CreateBrowse()` function, by doing something like this:

>     bfIcons[BF_PBM] = MakePix1(br->win, br_pbm_bits,
>                                br_pbm_width, br_pbm_height);

Hook your format into the `scanFile()` function. Find the following code:

>     switch (filetype) {
>     case RFT_GIF:      bf->ftype = BF_GIF;      break;
>     case RFT_PM:       bf->ftype = BF_PM;       break;

etc...

And add a case for your format. (To map `RFT_*` values into their corresponding `BF_*` values.)

Hook your format into the `genIcon()` function. Find the following code:

>     sprintf(str, "%dx%d ", pinfo.w, pinfo.h);
>     switch (filetype) {
>       case RFT_GIF:      if (strstr(pinfo.shrtInfo, "GIF89"))
>                            strcat(str,"GIF89 file");
>                          else
>                            strcat(str,"GIF87 file");
>       break;
>       case RFT_PM:       strcat(str,"PM file");  break

And add a case for your format. This generates an appropriate info string that gets put in the icon files maintained by the visual schnauzer (and displayed whenever you click on an icon in the schnauzer window).

That should do it. Consult the files `xv.h, xv.c, xvbrowse.c,` and `xvpbm.c` for any further specifics.