
Appendix G:	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 write PostScript files, but it can't read them.

The following instructions were written as I added PBM/PGM/PPM
capability to the program, so they're likely to be fairly accurate.
For example purposes, I'll be talking about the PBM/PGM/PPM code
specifically.  (See the file xvpbm.c for full details.)


Section G.1:  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.
If you're loading an 8-bit colormapped image, such as a GIF image,
no problem.  If you're loading an 8-or-less-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.  And if you're loading a 24 bit RGB file,
you'll have to compress it down to 8 bits by calling Conv24to8().

Make a copy of xvpm.c, calling it something appropriate.  I'm
adding PBM capabilities, so I think xvpbm.c is a fine file name.

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.

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 and the number of colors available on this
display, like so:

/*******************************************/
int LoadPBM(fname,nc)
    char *fname;   int nc;
/*******************************************/

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.
Your Load() routine is guaranteed that it will be reading from a real
file, not a stream.  This lets you use routines such as fseek(), and
such.

The number of colors argument is either going to be 2^n, where n is the
number of bitplanes on your display, or 'ncols', if specified on
the command line.  In either case, this number will only come into
play if you have to do a 24-to-8 bit conversion.  More on that later.

The Load() function returns '0' on success, non-zero on failure.

This function is expected to load up the following global variables:

byte *pic;
	this is a wide*high array of bytes, one byte per pixel,
starting at the top-left corner, and proceeding in scan-line order.
There is no padding of any sort at the end of a scan line.  The Load()
function is expected to malloc() the memory for this image.

int pWIDE, pHIGH;
	these variables specify the size of the image that has been
loaded, in pixels.

byte r[256], g[256], b[256];
	the desired colormap.  As specified above, 'pic' is an
8-bits per pixel 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 the 'pic' need to be set.  For
example, if the pic is known to be a B/W bitmap with pixel values of 0
and 1, you'd only have to set entries '0' and '1' of
the r,g,b arrays.

char *formatStr;
	a short character string describing the format and size of the
image.  For example, "320x200 PBM".

The function should also call 'SetISTR(ISTR_FORMAT, fmt, args)'
to set the "Format:" string in the xv info window.  It should
call the function as soon as possible (i.e., once it knows the format
of the picture, but before it has tried to load/parse all of the image
data.)  Note that the "Format:" string in the xv info window
should be set to a somewhat more verbose version of formatStr.  See
the source code for examples.

The Load() function should also call 'SetDirRButt(F_FORMAT, ...)' to
set the default format (in the xv save window) to the format of the
loaded file.  This, of course, is only relevant if you will also be
able write files in your new format.  If you aren't planning to have a
Write() function for this format, you won't have a listing for this
format in the xv save window.


Section G.1.1:  Error Handling

Non-fatal errors in your Load() routine should be handled by calling
SetISTR(ISTR_WARNING, fmt, args...), and returning a non-zero value.
The error string will be displayed in the xv controls and xv info
windows.

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.

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.


Section G.1.2:  Loading 24-bit RGB Formats

If (as in the case of PPM files) your file format has 24 bits of
information per pixel, you'll have to get it down to 8 bits and a
colormap for xv to make any use of it.  Conveniently, a function
'Conv24to8(pic24, w, h, nc)' is provided, so don't worry
about it.

To use it, you'll have to load your picture into a pWIDE*pHIGH*3
array of bytes.  (You'll be expected to malloc() this array.)  This
array begins at the top left corner, and proceeds in scan-line order.
The first byte of the array is the red component of pixel0, followed
by the green component of pixel0, followed by the blue component of
pixel0, followed by the red component of pixel1, etc... There is no
padding of any kind.

Once you've got this image loaded, call Conv24to8() with a pointer
to the 24bit image, the width and height of the image, and the number
of colors to 'shoot for' in the resulting 8-bit picture.  This
is the same parameter that was passed in to your Load() routine, so
just pass it along.

If successful, Conv24to8() will return '0'.  It will have
created and generated the pic array, and filled in the pWIDE and pHIGH
variables, and the r[], g[], b[] arrays.  You should now free() the
memory associated with the 24-bit version of your image and leave your
Load() function.

Read the source in xvpbm.c for further info on writing the Load()
routine.

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

Edit xv.h and add two function prototypes for any global functions
you've written (presumably just LoadPBM() in this case).  You'll
need to add a full function prototype (with parameter types) in the
#ifdef __STDC__ section (near the bottom), and a function reference
(just the return type) in the #else /* non-ANSI */ section at the
bottom.

Edit xv.c:

*	Add a filetype #define near the top.  Find the following section:

		/* file types that can be read */
		#define UNKNOWN 0
		#define GIF     1
		#define PM      2

	and add one more to the list, in this case: "#define PBM 3"

	Note: I only added one filetype to this list, despite the fact
that I'm really adding three (or six, really) different file
formats to the program (PBM, PGM, and PPM, in 'raw' and
'ascii' variations).  This is because all of these file formats
are related, and are handled by the same Load() function.

*	Now tell the openPic() routine about your Load() routine:

	find the following (in openPic()):

		filetype = UNKNOWN;
		if (strncmp(magicno,"GIF87a",6)==0 ||
		    strncmp(magicno,"GIF89a",6)==0) filetype = GIF;
		else if (strncmp(magicno,"VIEW",4)==0 ||
		         strncmp(magicno,"WEIV",4)==0) filetype = PM;

	Add another 'else' case that will set filetype if the
file appears to be in your format:

		else if (magicno[0] == 'P' && magicno[1]>='1' &&
		         magicno[1]<='6') filetype = PBM;


	And add another case to the switch statement (a few lines further down)

		switch (filetype) {
		case GIF: i = LoadGIF(filename,ncols);  break;
		case PM:  i = LoadPM(filename,ncols);   break;
		}

	add:

		case PBM: i = LoadPBM(filename,ncols); break;


That should do it.  Consult the files xvpm.c or xvpbm.c for further
information.  Remember: do as I mean, not as I say.


Section G.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 8-bit colormapped images.  As a
result, writing out 24-bit RGB images is a horrible waste (unless your
format does some clever file compression), and is to be avoided if
your file format can handle colortable images.

If you haven't already done so (if/when you created the Load()
function): *	Make a copy of xvpm.c, calling it something
appropriate.  I'm adding PBM capabilities, so I think xvpbm.c is a
fine file name.

*	Edit the Makefile and/or the Imakefile so that your new module
will be compiled.  Add 'xvpbm.o' to the OBJS macro in the
Makefile, and to the OBJS1 macro in the Imakefile.

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 virtual
plethora of arguments.  At a minimum, you'll be given a FILE * to
an already open-for-writing stream, a pointer to an 8-bits per pixel
image, the width and height of that image, pointers to 256-entry red,
green, and blue colormaps, the number of colors actually used in the
colormaps, and the 'color style' from the xv save window.

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.)

Your function definition should look something like this:

/*******************************************/
int WritePBM(fp,pic,w,h,rmap,gmap,bmap,
             numcols,colorstyle,raw)
    FILE *fp;
    byte *pic;
    int   w,h;
    byte *rmap, *gmap, *bmap;
    int   numcols, colorstyle, raw;
/*******************************************/

Write the function as you deem appropriate.

Some Notes:
*	your function should return '0' if successful, non-zero if not
*	don't close 'fp'
*	pic is a w*h byte array, starting at top-left, and proceeding
	in normal scan-line order
*	colorstyle can (currently) take on three values:
F_FULLCOLOR: This could mean either 24-bit RGB, or an 8-bit colormap
or any other color format.  r[pix],g[pix],b[pix] specify the color of
pixel 'pix'.

F_GREYSCALE: preferably 8 bits.  Two caveats: you must use the
colormap to determine what grey value to write.  For all you know,
pixel value '0' in pic could map to white, '1' could map to black, and
'2' could map to a half-intensity grey.  You cannot make the
assumption that pixel values of '0' are black, and pixel values of
'255' are white.

	The other note: unless the original picture was a greyscale,
(which shouldn't be tested for), the colormap is going to have actual
colors in it.  You'll want to map RGB colors into greyscale values
using 'the standard formula' (roughly .33R + .5G +.17B).  The
following code shows how to quickly write a raw greyscale image:

	if (colorstyle == F_GREYSCALE) {
	  byte rgb[256];
	  for (i=0; i<numcols; i++) 
	    rgb[i] = MONO(rmap[i],gmap[i],bmap[i]);

	  for (i=0, p=pic; i<w*h; i++, p++)
	    putc(rgb[*p],fp);
	}

F_BWDITHER: The stippling algorithm will have already been performed
by the time your function is called.  pic will be an image consisting
of the pixel values '1' (white) and '0' (white).  pic will still be
organized in the same way (i.e., one byte per pixel).

Note: for F_FULLCOLOR or F_GREYSCALE images, you will be guaranteed
that all pixel values in pic will be in the range [0 - numcols-1]
(inclusive).


That done, edit 'xv.h' and add a pair of function declarations for
your new function (one full ANSI-style prototype, and one that just
declares the return type).  Copy the declarations for 'WritePM()'.

Also find the block:
#define F_GIF      0
#define F_PM       1
and add another line (or two, in this case)
#define F_PBMRAW   2
#define F_PBMASCII 3

These numbers must be contiguous, as they are used as indices into the
formatRB array.

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

Add another format type to the 'formatRB' button list:

In the function 'CreateDirW()', find the block that (starts like):

	formatRB = RBCreate(NULL,dirW,26,y,"GIF",infofg,infobg);
	RBCreate(formatRB,dirW,26,y+18,"PM",infofg,infobg);

copy the last 'RBCreate' call in the list, add '18' to the 'y+**'
argument, and stick in an appropriate format type name.  In this case,
I'm adding two formats (PBM raw and PBM ascii) so I'll add these two
lines:

	RBCreate(formatRB, dirW, 26, y+36,
	         "PBM (raw)", infofg, infobg);
	RBCreate(formatRB, dirW, 26, y+54, 
	         "PBM (ascii)", infofg, infobg);

Note: The RBCreate() calls must be done in the same order as the
F_GIF, F_PM, etc. macros were defined in xv.h.


In the function DoSave(), find the following block:
	switch (fmt) {
	case F_GIF:
	  rv = WriteGIF(fp,thepic,w, h, r, g, b,nc,col); break;
	case F_PM:
	  rv = WritePM (fp,thepic,w, h, r, g, b,nc,col); break;
	}

and add cases for your function(s), like so:

	case F_PBMRAW:
	  rv = WritePBM(fp,thepic,w, h, r, g, b,nc,col,1); break;
	case F_PBMASCII:
	  rv = WritePBM(fp,thepic,w, h, r, g, b,nc,col,0); break;

That should do it!


Section G.2.1:  Writing Complex Formats

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.

This is not recommended for anyone who doesn't understand Xlib programming.

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.

