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